domma-cms 0.14.8 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- const targets=document.querySelectorAll("[data-form]");targets.length&&targets.forEach(initFormTarget);function showMessage(i,r,t){const e=i.querySelector(".fb-form-success, .fb-form-error");e&&e.remove();const a=document.createElement("div");a.className=t==="success"?"fb-form-success":"fb-form-error",a.textContent=r,i.appendChild(a)}function attachRuntimeLifecycle(i,r){if(i._formLogicRuntime=r,!i.parentNode||typeof MutationObserver>"u")return;const t=new MutationObserver(function(e){for(const a of e)for(const n of a.removedNodes)if(n===i||n.nodeType===1&&n.contains&&n.contains(i)){r.destroy(),t.disconnect();return}});t.observe(i.parentNode,{childList:!0,subtree:!1})}function buildBlueprintFromFields(i,r){const t={};return i.forEach(function(e){if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const a=e.type==="checkbox"?"boolean":e.type==="date"?"string":e.type,n={...e.formConfig||{}};n.span==="full"&&r&&(n.span=r),t[e.name]={type:a,label:e.label||e.name,required:e.required||!1,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...n}},e.minLength!==void 0&&(t[e.name].minLength=e.minLength),e.maxLength!==void 0&&(t[e.name].maxLength=e.maxLength),e.min!==void 0&&(t[e.name].min=e.min),e.max!==void 0&&(t[e.name].max=e.max)}),t}function buildInitialData(i){const r={};return i.forEach(function(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&&(r[t.name]=typeof e=="object"?e.value:e)}}),r}function patchDateInputs(i,r){(r||[]).forEach(function(t){if(t.type!=="date"||!t.name)return;const e=i.querySelector('[name="'+t.name+'"]');e&&e.type!=="date"&&(e.type="date")})}function buildWizardSteps(i,r){const t=[];let e=[],a=r||"Step 1",n="";return i.forEach(function(u){u.type==="page-break"?(t.push({title:a,description:n,fieldGroup:e}),e=[],a=u.label||"Step "+(t.length+1),n=u.description||""):u.type!=="spacer"&&e.push(u)}),t.push({title:a,description:n,fieldGroup:e}),t}function injectHoneypot(i){const r=document.createElement("div");r.className="fb-form-honeypot",r.setAttribute("aria-hidden","true");const t=document.createElement("input");t.name="website",t.type="text",t.tabIndex=-1,t.autocomplete="url",t.placeholder="https://",r.appendChild(t);const e=document.createElement("input");e.name="_t",e.type="hidden",e.value=Date.now(),r.appendChild(e),i.appendChild(r)}function injectSpacers(i,r){const t=i.querySelector("form");if(!t)return;const e=Array.from(t.querySelectorAll(".form-group"));let a=0;r.forEach(function(n){if(n.type==="spacer"){const u=document.createElement("div");u.className="fb-spacer";const s=e[a];if(s)t.insertBefore(u,s);else{const l=t.querySelector('[type="submit"]');l?t.insertBefore(u,l):t.appendChild(u)}}else n.type!=="page-break"&&a++})}function submitForm(i,r,t,e,a){const n=a||e,u=n.querySelector('[name="website"]')?.value||"",s=n.querySelector('[name="_t"]')?.value||"",l=Object.assign({},r,{_hp:u,_t:s});return fetch("/api/forms/submit/"+i,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(l)}).then(m=>m.json().then(c=>({ok:m.ok,body:c}))).then(m=>{m.ok&&m.body.ok?(e.textContent="",showMessage(e,m.body.message||t.successMessage||"Thank you.","success")):showMessage(e,m.body.error||"Something went wrong.","error")}).catch(()=>{showMessage(e,"Unable to submit. Please check your connection.","error")})}function renderManualForm(i,r,t,e,a){const n=document.createElement("form");n.noValidate=!0,r.forEach(function(s){const l=document.createElement("div");l.className="form-group",l.style.marginBottom="1.25rem";const m=document.createElement("label");if(m.className="form-label",m.textContent=s.label||s.name,s.required){const o=document.createElement("span");o.textContent=" *",o.style.color="#f87171",m.appendChild(o)}let c;s.type==="textarea"?(c=document.createElement("textarea"),c.rows=s.formConfig?.rows||4,c.className="form-input"):s.type==="select"?(c=document.createElement("select"),c.className="form-input",(s.options||[]).forEach(function(o){const p=document.createElement("option");p.value=typeof o=="string"?o:o.value??"",p.textContent=typeof o=="string"?o:o.label||p.value,c.appendChild(p)})):(c=document.createElement("input"),c.type=s.type||"text",c.className="form-input",s.placeholder&&(c.placeholder=s.placeholder)),c.name=s.name,c.required=s.required||!1,l.appendChild(m),l.appendChild(c),n.appendChild(l)}),t.honeypot&&injectHoneypot(n);const u=document.createElement("button");u.type="submit",u.className="btn btn-primary",u.textContent=t.submitText||"Submit",n.appendChild(u),n.addEventListener("submit",function(s){s.preventDefault();const l={};if(r.forEach(function(m){const c=n.querySelector('[name="'+m.name+'"]');c&&(l[m.name]=c.value)}),window.FormLogicEngine&&a){const m=window.FormLogicEngine,c=[],o=[];if(r.forEach(function(p){if(m.evaluateFieldVisibility(p,l)==="hidden"){delete l[p.name];return}const h=m.evaluateFieldRequirement(p,l),f=l[p.name];h&&(!f||!String(f).trim())&&c.push(p.label||p.name);const d=m.validateField(p,f||"",l);d.length&&o.push(d[0].message)}),c.length||o.length){const p=[];c.length&&p.push("Required: "+c.join(", ")),o.length&&p.push(o.join("; ")),showMessage(i,p.join(". "),"error");return}}i.classList.add("fb-form-loading"),u.disabled=!0,submitForm(e,l,t,i,n).finally(function(){i.classList.remove("fb-form-loading"),u.disabled=!1})}),i.appendChild(n),window.FormLogicEngine&&a&&r.some(s=>s.logic)&&new window.FormLogicEngine.FormLogicRuntime(a,i).init()}function initFormTarget(i){const r=i.getAttribute("data-form");r&&fetch("/api/forms/"+r+"/public").then(t=>{if(!t.ok)throw new Error("Form not found: "+r);return t.json()}).then(t=>{const e=t.fields||[],a=t.settings||{},n=document.createElement("div");n.className="fb-form-wrapper",i.appendChild(n);const u=e.some(s=>s.type==="page-break");if(typeof Domma<"u"&&Domma.forms){const s=a.columns||1;if(u&&Domma.forms.wizard){const m=buildWizardSteps(e,t.title).map(function(o){return{title:o.title,description:o.description,fields:buildBlueprintFromFields(o.fieldGroup,s)}}),c=Domma.forms.wizard(n,{schema:{steps:m},onSubmit:function(o){return submitForm(r,o,a,n,null)}});Promise.resolve(c).then(function(){if(patchDateInputs(n,e),window.FormLogicEngine&&e.some(o=>o.logic)){const o=new window.FormLogicEngine.FormLogicRuntime(t,n);o.init(),attachRuntimeLifecycle(n,o)}if(a.honeypot){const o=n.querySelector("form");o&&injectHoneypot(o)}})}else if(Domma.forms.render){const l=buildBlueprintFromFields(e,s),m=buildInitialData(e),c=Domma.forms.render(n,l,m,{submitText:a.submitText||"Submit",layout:a.layout||"stacked",columns:s,onSubmit:function(o){return submitForm(r,o,a,n,null)}});Promise.resolve(c).then(function(){if(patchDateInputs(n,e),window.FormLogicEngine&&e.some(o=>o.logic)){const o=new window.FormLogicEngine.FormLogicRuntime(t,n);o.init(),attachRuntimeLifecycle(n,o)}if(e.some(o=>o.type==="spacer")&&injectSpacers(n,e),a.honeypot){const o=n.querySelector("form");o&&injectHoneypot(o)}})}}else renderManualForm(n,e.filter(s=>s.type!=="page-break"&&s.type!=="spacer"),a,r,t)}).catch(t=>{const e=document.createElement("p");e.textContent="Form unavailable.",e.style.cssText="color:#f87171;font-style:italic;",i.appendChild(e),console.warn("[forms]",t.message)})}
1
+ const targets=document.querySelectorAll("[data-form]");targets.length&&targets.forEach(initFormTarget);function showMessage(i,r,t){const e=i.querySelector(".fb-form-success, .fb-form-error");e&&e.remove();const a=document.createElement("div");a.className=t==="success"?"fb-form-success":"fb-form-error",a.textContent=r,i.appendChild(a)}function attachRuntimeLifecycle(i,r){if(i._formLogicRuntime=r,!i.parentNode||typeof MutationObserver>"u")return;const t=new MutationObserver(function(e){for(const a of e)for(const n of a.removedNodes)if(n===i||n.nodeType===1&&n.contains&&n.contains(i)){r.destroy(),t.disconnect();return}});t.observe(i.parentNode,{childList:!0,subtree:!1})}function buildBlueprintFromFields(i,r){const t={};return i.forEach(function(e){if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const a=e.type==="checkbox"?"boolean":e.type==="date"?"string":e.type,n={...e.formConfig||{}};n.span==="full"&&r&&(n.span=r),t[e.name]={type:a,label:e.label||e.name,required:e.required||!1,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{helperText:e.helper},...e.tooltip&&{tooltip:e.tooltip},...n}},e.minLength!==void 0&&(t[e.name].minLength=e.minLength),e.maxLength!==void 0&&(t[e.name].maxLength=e.maxLength),e.min!==void 0&&(t[e.name].min=e.min),e.max!==void 0&&(t[e.name].max=e.max)}),t}function buildInitialData(i){const r={};return i.forEach(function(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&&(r[t.name]=typeof e=="object"?e.value:e)}}),r}function patchDateInputs(i,r){(r||[]).forEach(function(t){if(t.type!=="date"||!t.name)return;const e=i.querySelector('[name="'+t.name+'"]');e&&e.type!=="date"&&(e.type="date")})}function buildWizardSteps(i,r){const t=[];let e=[],a=r||"Step 1",n="";return i.forEach(function(u){u.type==="page-break"?(t.push({title:a,description:n,fieldGroup:e}),e=[],a=u.label||"Step "+(t.length+1),n=u.description||""):u.type!=="spacer"&&e.push(u)}),t.push({title:a,description:n,fieldGroup:e}),t}function injectHoneypot(i){const r=document.createElement("div");r.className="fb-form-honeypot",r.setAttribute("aria-hidden","true");const t=document.createElement("input");t.name="website",t.type="text",t.tabIndex=-1,t.autocomplete="url",t.placeholder="https://",r.appendChild(t);const e=document.createElement("input");e.name="_t",e.type="hidden",e.value=Date.now(),r.appendChild(e),i.appendChild(r)}function injectSpacers(i,r){const t=i.querySelector("form");if(!t)return;const e=Array.from(t.querySelectorAll(".form-group"));let a=0;r.forEach(function(n){if(n.type==="spacer"){const u=document.createElement("div");u.className="fb-spacer";const s=e[a];if(s)t.insertBefore(u,s);else{const l=t.querySelector('[type="submit"]');l?t.insertBefore(u,l):t.appendChild(u)}}else n.type!=="page-break"&&a++})}function submitForm(i,r,t,e,a){const n=a||e,u=n.querySelector('[name="website"]')?.value||"",s=n.querySelector('[name="_t"]')?.value||"",l=Object.assign({},r,{_hp:u,_t:s});return fetch("/api/forms/submit/"+i,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(l)}).then(m=>m.json().then(c=>({ok:m.ok,body:c}))).then(m=>{m.ok&&m.body.ok?(e.textContent="",showMessage(e,m.body.message||t.successMessage||"Thank you.","success")):showMessage(e,m.body.error||"Something went wrong.","error")}).catch(()=>{showMessage(e,"Unable to submit. Please check your connection.","error")})}function renderManualForm(i,r,t,e,a){const n=document.createElement("form");n.noValidate=!0,r.forEach(function(s){const l=document.createElement("div");l.className="form-group",l.style.marginBottom="1.25rem";const m=document.createElement("label");if(m.className="form-label",m.textContent=s.label||s.name,s.required){const o=document.createElement("span");o.textContent=" *",o.style.color="#f87171",m.appendChild(o)}let c;s.type==="textarea"?(c=document.createElement("textarea"),c.rows=s.formConfig?.rows||4,c.className="form-input"):s.type==="select"?(c=document.createElement("select"),c.className="form-input",(s.options||[]).forEach(function(o){const p=document.createElement("option");p.value=typeof o=="string"?o:o.value??"",p.textContent=typeof o=="string"?o:o.label||p.value,c.appendChild(p)})):(c=document.createElement("input"),c.type=s.type||"text",c.className="form-input",s.placeholder&&(c.placeholder=s.placeholder)),c.name=s.name,c.required=s.required||!1,l.appendChild(m),l.appendChild(c),n.appendChild(l)}),t.honeypot&&injectHoneypot(n);const u=document.createElement("button");u.type="submit",u.className="btn btn-primary",u.textContent=t.submitText||"Submit",n.appendChild(u),n.addEventListener("submit",function(s){s.preventDefault();const l={};if(r.forEach(function(m){const c=n.querySelector('[name="'+m.name+'"]');c&&(l[m.name]=c.value)}),window.FormLogicEngine&&a){const m=window.FormLogicEngine,c=[],o=[];if(r.forEach(function(p){if(m.evaluateFieldVisibility(p,l)==="hidden"){delete l[p.name];return}const h=m.evaluateFieldRequirement(p,l),f=l[p.name];h&&(!f||!String(f).trim())&&c.push(p.label||p.name);const d=m.validateField(p,f||"",l);d.length&&o.push(d[0].message)}),c.length||o.length){const p=[];c.length&&p.push("Required: "+c.join(", ")),o.length&&p.push(o.join("; ")),showMessage(i,p.join(". "),"error");return}}i.classList.add("fb-form-loading"),u.disabled=!0,submitForm(e,l,t,i,n).finally(function(){i.classList.remove("fb-form-loading"),u.disabled=!1})}),i.appendChild(n),window.FormLogicEngine&&a&&r.some(s=>s.logic)&&new window.FormLogicEngine.FormLogicRuntime(a,i).init()}function initFormTarget(i){const r=i.getAttribute("data-form");r&&fetch("/api/forms/"+r+"/public").then(t=>{if(!t.ok)throw new Error("Form not found: "+r);return t.json()}).then(t=>{const e=t.fields||[],a=t.settings||{},n=document.createElement("div");n.className="fb-form-wrapper",i.appendChild(n);const u=e.some(s=>s.type==="page-break");if(typeof Domma<"u"&&Domma.forms){const s=a.columns||1;if(u&&Domma.forms.wizard){const m=buildWizardSteps(e,t.title).map(function(o){return{title:o.title,description:o.description,fields:buildBlueprintFromFields(o.fieldGroup,s)}}),c=Domma.forms.wizard(n,{schema:{steps:m},onSubmit:function(o){return submitForm(r,o,a,n,null)}});Promise.resolve(c).then(function(){if(patchDateInputs(n,e),window.FormLogicEngine&&e.some(o=>o.logic)){const o=new window.FormLogicEngine.FormLogicRuntime(t,n);o.init(),attachRuntimeLifecycle(n,o)}if(a.honeypot){const o=n.querySelector("form");o&&injectHoneypot(o)}})}else if(Domma.forms.render){const l=buildBlueprintFromFields(e,s),m=buildInitialData(e),c=Domma.forms.render(n,l,m,{submitText:a.submitText||"Submit",layout:a.layout||"stacked",columns:s,onSubmit:function(o){return submitForm(r,o,a,n,null)}});Promise.resolve(c).then(function(){if(patchDateInputs(n,e),window.FormLogicEngine&&e.some(o=>o.logic)){const o=new window.FormLogicEngine.FormLogicRuntime(t,n);o.init(),attachRuntimeLifecycle(n,o)}if(e.some(o=>o.type==="spacer")&&injectSpacers(n,e),a.honeypot){const o=n.querySelector("form");o&&injectHoneypot(o)}})}}else renderManualForm(n,e.filter(s=>s.type!=="page-break"&&s.type!=="spacer"),a,r,t)}).catch(t=>{const e=document.createElement("p");e.textContent="Form unavailable.",e.style.cssText="color:#f87171;font-style:italic;",i.appendChild(e),console.warn("[forms]",t.message)})}
package/public/js/site.js CHANGED
@@ -1 +1 @@
1
- $(()=>{const b=window.__CMS_NAV__||{},d=window.__CMS_SITE__||{};if(d.autoTheme?.enabled){let e=function(s){const f=(s||"07:00").split(":");return+f[0]*60+(+f[1]||0)},o=function(){const s=new Date,f=s.getHours()*60+s.getMinutes();return f>=e(t.dayStart)&&f<e(t.nightStart)?t.dayTheme:t.nightTheme};var r=e,u=o;const t=d.autoTheme;Domma.theme.set(o()),setInterval(()=>Domma.theme.set(o()),6e4)}if($("#site-navbar").length&&b.brand){const t={...b.brand},e=t.size&&t.size!=="md"?` navbar-brand-${t.size}`:"";if(t.logo||t.icon||t.tagline){let o="";t.logo?o+=`<img src="${t.logo}" class="navbar-brand-logo" alt="${t.text||""}">`:t.icon&&(o+=`<span data-icon="${t.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),t.text&&(o+=`<span class="navbar-brand-text${e}">${t.text}</span>`),t.tagline&&(o+=`<small class="navbar-brand-tagline">${t.tagline}</small>`),t.html=o}else e&&t.text&&(t.html=`<span class="navbar-brand-text${e}">${t.text}</span>`);Domma.elements.navbar("#site-navbar",{brand:t,items:b.items||[],variant:b.variant||"dark",position:b.position||"sticky",collapsible:!0}),Domma.icons.scan("#site-navbar")}const y=$("#site-footer");if(y.length){const t=d.social||{},e={twitter:{label:"X / Twitter",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.742l7.73-8.835L1.254 2.25H8.08l4.259 5.629L18.244 2.25zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>'},facebook:{label:"Facebook",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>'},instagram:{label:"Instagram",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>'},linkedin:{label:"LinkedIn",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>'},github:{label:"GitHub",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>'},youtube:{label:"YouTube",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.495 6.205a3.007 3.007 0 00-2.088-2.088c-1.87-.501-9.396-.501-9.396-.501s-7.507-.01-9.396.501A3.007 3.007 0 00.527 6.205a31.247 31.247 0 00-.522 5.805 31.247 31.247 0 00.522 5.783 3.007 3.007 0 002.088 2.088c1.868.502 9.396.502 9.396.502s7.506 0 9.396-.502a3.007 3.007 0 002.088-2.088 31.247 31.247 0 00.5-5.783 31.247 31.247 0 00-.5-5.805zM9.609 15.601V8.408l6.264 3.602z"/></svg>'}};let o='<div class="footer-inner container">';if(d.footer){const m=d.footer;o+=`<p>${m.copyright||""}</p>`,m.links?.length&&(o+='<nav class="footer-links">',m.links.forEach(n=>{o+=`<a href="${n.url}">${n.text}</a>`}),o+="</nav>");const i=Object.keys(e).filter(n=>t[n]);i.length&&(o+='<div class="footer-social">',i.forEach(n=>{const{label:a,svg:c}=e[n];o+=`<a href="${t[n]}" target="_blank" rel="noopener noreferrer" aria-label="${a}" class="footer-social-link">${c}</a>`}),o+="</div>")}o+="</div>",y.html(o);const s=S.get("reduced_motion"),f=s!==null?!!s:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),h=y.get(0).querySelector(".footer-inner");if(h){const m=document.createElement("input");m.type="checkbox",m.className="form-switch-input",m.id="dm-motion-switch",m.checked=f,m.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const i=document.createElement("span");i.className="form-switch-label",i.textContent="Reduce motion";const n=document.createElement("label");n.className="form-switch footer-motion-switch",n.title="Reduce motion",n.appendChild(m),n.appendChild(i),h.appendChild(n)}}$("#site-sidebar").length&&Domma.elements.sidebar("#site-sidebar",{autoGenerate:!0,selector:"h2, h3",collapsible:!1,push:!0,contentSelector:".site-content"}),Domma.icons.scan();const p=$(".page-body");if(p.length){p.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),p.find(".tabs").each(function(){Domma.elements.tabs(this)}),p.find(".carousel").each(function(){Domma.elements.carousel(this,{autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide"})}),p.find(".dm-countdown").each(function(){const t={autoStart:!0};this.dataset.to&&(t.targetDate=new Date(this.dataset.to)),this.dataset.duration&&(t.duration=parseInt(this.dataset.duration,10)),this.dataset.format&&(t.format=this.dataset.format),Domma.elements.timer(this,t)}),p.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),p.find(".dm-progression").each(function(){Domma.elements.progression(this,{layout:this.dataset.layout||"vertical",theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0})});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(t=>{const e=t.dataset.revealMode||"stagger",o=t.dataset.revealAnimation||"slide-up",s=parseInt(t.dataset.revealDuration,10)||400,f=parseInt(t.dataset.revealStagger,10)||60,h=parseInt(t.dataset.revealDelay,10)||0,m=t.dataset.revealDirection==="rtl",i=Array.from(t.children),n={"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)"};i.forEach((a,c)=>{a.style.opacity="0",a.style.transform=n[o]||"",a.style.transition=`opacity ${s}ms ease, transform ${s}ms ease`;const l=m?i.length-1-c:c;a.style.transitionDelay=e==="stagger"?`${h+l*f}ms`:`${h}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const a=new IntersectionObserver(c=>{c.forEach(l=>{l.isIntersecting&&(l.target.offsetWidth,l.target.style.opacity="1",l.target.style.transform="none",a.unobserve(l.target))})},{threshold:.1});i.forEach(c=>a.observe(c))}))}),p.find(".card[data-collapsible]").each(function(){const t=this.querySelector(".card-header");t&&t.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),p.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const t=this.dataset.soTarget,e=document.getElementById(t);if(!e)return;const o=E.slideover({title:e.dataset.soTitle||"",size:e.dataset.soSize||"md",position:e.dataset.soPosition||"right"});e.style.display="",o.setContent(e),o.open()})})}if(typeof $.setup=="function"){const t=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(e=>{try{const o=atob(e.dataset.config),s=JSON.parse(o);Object.assign(t,s)}catch{}}),Object.keys(t).length>0){const e={};for(const[o,s]of Object.entries(t)){const f=s?.events?.click,{confirm:h,toast:m,alert:i,prompt:n,...a}=f||{};h||m||i||n?($(o).on("click",async function(l){if(l.preventDefault(),h&&!await E.confirm(h))return;let w=null;if(!(n&&(w=await E.prompt(n,{inputPlaceholder:a.promptPlaceholder||"",inputValue:a.promptDefault||""}),w===null))){if(a.target){const C=$(a.target);a.toggleClass&&C.toggleClass(a.toggleClass),a.addClass&&C.addClass(a.addClass),a.removeClass&&C.removeClass(a.removeClass),w!==null&&(a.setText&&C.text(w),a.setVal&&C.val(w),a.setAttr&&C.attr(a.setAttr,w))}a.href&&(window.location.href=a.href),m&&E.toast(m,{type:a.toastType||"success"}),i&&E.alert(i)}}),Object.keys(a).length&&(e[o]={...s,events:{...s.events,click:a}})):e[o]=s}$.setup(e)}}p.length&&wireCTAButtons(p.get(0))});function wireCTAButtons(b){b.querySelectorAll(".dm-cta-trigger").forEach(d=>{d.addEventListener("click",async()=>{const v=d.dataset.action,y=d.dataset.entry,g=d.dataset.confirm;let p=S.get("auth_token");if(!p){E.toast("Please log in to perform this action.",{type:"warning"});return}if(g&&!await E.confirm(g))return;const r=Array.from(d.childNodes).map(t=>t.cloneNode(!0));d.disabled=!0,d.textContent="Running\u2026";const u=t=>fetch(`/api/actions/${v}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`},body:JSON.stringify({entryId:y})});try{let t=await u(p);if(t.status===401){const o=S.get("auth_refresh_token");if(o){const s=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:o})});if(s.ok){const{token:f}=await s.json();S.set("auth_token",f),p=f,t=await u(p)}}}const e=await t.json().catch(()=>({}));if(!t.ok)throw new Error(e.error||e.message||`Error ${t.status}`);E.toast(e.message||"Action completed.",{type:"success"})}catch(t){E.toast(t.message||"Action failed.",{type:"error"})}finally{d.disabled=!1,d.textContent="",r.forEach(t=>d.appendChild(t)),Domma.icons.scan(d)}})})}(function(){const d=document.querySelectorAll("[data-collection-table]");!d.length||typeof T>"u"||!T.create||d.forEach(v=>{let y;try{y=JSON.parse(atob(v.dataset.payload))}catch{return}const{columns:g,rows:p,search:r,sortable:u,exportable:t,pageSize:e,empty:o,ctaConfig:s}=y;if(!g?.length)return;if(s){const m=g.findIndex(i=>i.key==="_cta");m!==-1&&(g[m]={key:"_cta",title:"",render:(i,n)=>{const a=document.createElement("button");if(a.className=`btn btn-${s.style||"primary"} dm-cta-trigger`,a.dataset.action=s.action||"",a.dataset.entry=n._entryId||"",s.confirm&&(a.dataset.confirm=s.confirm),s.icon){const c=document.createElement("span");c.dataset.icon=s.icon,a.appendChild(c),a.appendChild(document.createTextNode(" "))}return a.appendChild(document.createTextNode(s.label||"Run")),a}})}const f="col-table-"+Math.random().toString(36).slice(2,7),h=document.createElement("div");h.id=f,v.replaceChildren(h),T.create("#"+f,{data:p,columns:g,search:r,sortable:u,exportable:t,pageSize:e,emptyMessage:o}),s&&wireCTAButtons(h)})})(),(function(){const d=document.querySelectorAll("[data-form-inline]");if(!d.length||typeof F>"u")return;function v(r,u){const t={};return(r||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const o=e.type==="checkbox"?"boolean":e.type==="date"?"string":e.type,s={...e.formConfig||{}};s.span==="full"&&u&&(s.span=u),t[e.name]={type:o,label:e.label,required:e.required,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...s}}}),t}function y(r){const u={};return(r||[]).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&&(u[t.name]=typeof e=="object"?e.value:e)}}),u}function g(r,u){(u||[]).forEach(t=>{if(t.type!=="date"||!t.name)return;const e=r.querySelector(`[name="${t.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function p(r,u,t){let e=r.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",r.appendChild(e)),e.textContent=u,e.style.cssText=t?"color:var(--danger,#f87171);margin-top:.75rem;":"color:var(--success,#4ade80);margin-top:.75rem;"}d.forEach(r=>{let u;try{u=JSON.parse(atob(r.dataset.formInline))}catch{return}const t=u.fields||[],e=u.settings||{},o=e.columns||1,s=e.layout||"stacked",f=t.some(i=>i.type==="page-break"),h=async i=>{try{const n=r.querySelector('[name="website"]'),a=r.querySelector('[name="_t"]'),c=Object.assign({},i);n!==null&&(c._hp=n.value),a!==null&&(c._t=a.value);const l=await H.post(`/api/forms/submit/${u.slug}`,c);if(l?.redirect){window.location.href=l.redirect;return}for(;r.firstChild;)r.removeChild(r.firstChild);p(r,l?.message||e.successMessage||"Thank you for your submission.",!1)}catch(n){throw p(r,n.message||"Submission failed. Please try again.",!0),n}};function m(i){const n=i.querySelector("form");if(!n)return;const a=document.createElement("div");a.className="fb-form-honeypot",a.setAttribute("aria-hidden","true");const c=document.createElement("input");c.name="website",c.type="text",c.tabIndex=-1,c.autocomplete="url",c.placeholder="https://",a.appendChild(c);const l=document.createElement("input");l.name="_t",l.type="hidden",l.value=Date.now(),a.appendChild(l),n.appendChild(a)}if(f&&F.wizard){const i=[];let n=[],a=u.title||"Step 1",c="";t.forEach(l=>{l.type==="page-break"?(i.push({title:a,description:c,fields:v(n,o)}),n=[],a=l.label||`Step ${i.length+1}`,c=l.description||""):l.type!=="spacer"&&n.push(l)}),i.push({title:a,description:c,fields:v(n,o)}),F.wizard(r,{schema:{steps:i},onSubmit:h}),g(r,t),e.honeypot!==!1&&m(r)}else if(F.render){if(F.render(r,v(t,o),y(t),{submitText:e.submitText||"Submit",layout:s,columns:o,onSubmit:h}),s==="grid"&&e.submitSpan==="full"){const i=r.querySelector(".form-buttons");i&&i.classList.add("col-span-full")}g(r,t),e.honeypot!==!1&&m(r)}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
1
+ $(()=>{const w=window.__CMS_NAV__||{},l=window.__CMS_SITE__||{};if(l.autoTheme?.enabled){let t=function(s){const h=(s||"07:00").split(":");return+h[0]*60+(+h[1]||0)},o=function(){const s=new Date,h=s.getHours()*60+s.getMinutes();return h>=t(e.dayStart)&&h<t(e.nightStart)?e.dayTheme:e.nightTheme};var i=t,d=o;const e=l.autoTheme;Domma.theme.set(o()),setInterval(()=>Domma.theme.set(o()),6e4)}if($("#site-navbar").length&&w.brand){const e={...w.brand},t=e.size&&e.size!=="md"?` navbar-brand-${e.size}`:"";if(e.logo||e.icon||e.tagline){let o="";e.logo?o+=`<img src="${e.logo}" class="navbar-brand-logo" alt="${e.text||""}">`:e.icon&&(o+=`<span data-icon="${e.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),e.text&&(o+=`<span class="navbar-brand-text${t}">${e.text}</span>`),e.tagline&&(o+=`<small class="navbar-brand-tagline">${e.tagline}</small>`),e.html=o}else t&&e.text&&(e.html=`<span class="navbar-brand-text${t}">${e.text}</span>`);Domma.elements.navbar("#site-navbar",{brand:e,items:w.items||[],variant:w.variant||"dark",position:w.position||"sticky",collapsible:!0}),Domma.icons.scan("#site-navbar")}const v=$("#site-footer");if(v.length){const e=l.social||{},t={twitter:{label:"X / Twitter",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.742l7.73-8.835L1.254 2.25H8.08l4.259 5.629L18.244 2.25zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>'},facebook:{label:"Facebook",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>'},instagram:{label:"Instagram",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>'},linkedin:{label:"LinkedIn",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>'},github:{label:"GitHub",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>'},youtube:{label:"YouTube",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.495 6.205a3.007 3.007 0 00-2.088-2.088c-1.87-.501-9.396-.501-9.396-.501s-7.507-.01-9.396.501A3.007 3.007 0 00.527 6.205a31.247 31.247 0 00-.522 5.805 31.247 31.247 0 00.522 5.783 3.007 3.007 0 002.088 2.088c1.868.502 9.396.502 9.396.502s7.506 0 9.396-.502a3.007 3.007 0 002.088-2.088 31.247 31.247 0 00.5-5.783 31.247 31.247 0 00-.5-5.805zM9.609 15.601V8.408l6.264 3.602z"/></svg>'}};let o='<div class="footer-inner container">';if(l.footer){const m=l.footer;o+=`<p>${m.copyright||""}</p>`,m.links?.length&&(o+='<nav class="footer-links">',m.links.forEach(n=>{o+=`<a href="${n.url}">${n.text}</a>`}),o+="</nav>");const f=Object.keys(t).filter(n=>e[n]);f.length&&(o+='<div class="footer-social">',f.forEach(n=>{const{label:a,svg:r}=t[n];o+=`<a href="${e[n]}" target="_blank" rel="noopener noreferrer" aria-label="${a}" class="footer-social-link">${r}</a>`}),o+="</div>")}o+="</div>",v.html(o);const s=S.get("reduced_motion"),h=s!==null?!!s:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),g=v.get(0).querySelector(".footer-inner");if(g){const m=document.createElement("input");m.type="checkbox",m.className="form-switch-input",m.id="dm-motion-switch",m.checked=h,m.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const f=document.createElement("span");f.className="form-switch-label",f.textContent="Reduce motion";const n=document.createElement("label");n.className="form-switch footer-motion-switch",n.title="Reduce motion",n.appendChild(m),n.appendChild(f),g.appendChild(n)}}$("#site-sidebar").length&&Domma.elements.sidebar("#site-sidebar",{autoGenerate:!0,selector:"h2, h3",collapsible:!1,push:!0,contentSelector:".site-content"}),Domma.icons.scan();const p=$(".page-body");if(p.length){p.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),p.find(".tabs").each(function(){Domma.elements.tabs(this)}),p.find(".carousel").each(function(){Domma.elements.carousel(this,{autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide"})}),p.find(".dm-countdown").each(function(){const e={autoStart:!0};this.dataset.to&&(e.targetDate=new Date(this.dataset.to)),this.dataset.duration&&(e.duration=parseInt(this.dataset.duration,10)),this.dataset.format&&(e.format=this.dataset.format),Domma.elements.timer(this,e)}),p.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),p.find(".dm-progression").each(function(){Domma.elements.progression(this,{layout:this.dataset.layout||"vertical",theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0})});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(e=>{const t=e.dataset.revealMode||"stagger",o=e.dataset.revealAnimation||"slide-up",s=parseInt(e.dataset.revealDuration,10)||400,h=parseInt(e.dataset.revealStagger,10)||60,g=parseInt(e.dataset.revealDelay,10)||0,m=e.dataset.revealDirection==="rtl",f=Array.from(e.children),n={"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)"};f.forEach((a,r)=>{a.style.opacity="0",a.style.transform=n[o]||"",a.style.transition=`opacity ${s}ms ease, transform ${s}ms ease`;const c=m?f.length-1-r:r;a.style.transitionDelay=t==="stagger"?`${g+c*h}ms`:`${g}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const a=new IntersectionObserver(r=>{r.forEach(c=>{c.isIntersecting&&(c.target.offsetWidth,c.target.style.opacity="1",c.target.style.transform="none",a.unobserve(c.target))})},{threshold:.1});f.forEach(r=>a.observe(r))}))}),p.find(".card[data-collapsible]").each(function(){const e=this.querySelector(".card-header");e&&e.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),p.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const e=this.dataset.soTarget,t=document.getElementById(e);if(!t)return;const o=E.slideover({title:t.dataset.soTitle||"",size:t.dataset.soSize||"md",position:t.dataset.soPosition||"right"});t.style.display="",o.setContent(t),o.open()})})}if(typeof $.setup=="function"){const e=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(t=>{try{const o=atob(t.dataset.config),s=JSON.parse(o);Object.assign(e,s)}catch{}}),Object.keys(e).length>0){const t={};for(const[o,s]of Object.entries(e)){const h=s?.events?.click,{confirm:g,toast:m,alert:f,prompt:n,...a}=h||{};g||m||f||n?($(o).on("click",async function(c){if(c.preventDefault(),g&&!await E.confirm(g))return;let u=null;if(!(n&&(u=await E.prompt(n,{inputPlaceholder:a.promptPlaceholder||"",inputValue:a.promptDefault||""}),u===null))){if(a.target){const y=$(a.target);a.toggleClass&&y.toggleClass(a.toggleClass),a.addClass&&y.addClass(a.addClass),a.removeClass&&y.removeClass(a.removeClass),u!==null&&(a.setText&&y.text(u),a.setVal&&y.val(u),a.setAttr&&y.attr(a.setAttr,u))}a.href&&(window.location.href=a.href),m&&E.toast(m,{type:a.toastType||"success"}),f&&E.alert(f)}}),Object.keys(a).length&&(t[o]={...s,events:{...s.events,click:a}})):t[o]=s}$.setup(t)}}p.length&&wireCTAButtons(p.get(0))});function wireCTAButtons(w){w.querySelectorAll(".dm-cta-trigger").forEach(l=>{l.addEventListener("click",async()=>{const C=l.dataset.action,v=l.dataset.entry,b=l.dataset.confirm;let p=S.get("auth_token");if(!p){E.toast("Please log in to perform this action.",{type:"warning"});return}if(b&&!await E.confirm(b))return;const i=Array.from(l.childNodes).map(e=>e.cloneNode(!0));l.disabled=!0,l.textContent="Running\u2026";const d=e=>fetch(`/api/actions/${C}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify({entryId:v})});try{let e=await d(p);if(e.status===401){const o=S.get("auth_refresh_token");if(o){const s=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:o})});if(s.ok){const{token:h}=await s.json();S.set("auth_token",h),p=h,e=await d(p)}}}const t=await e.json().catch(()=>({}));if(!e.ok)throw new Error(t.error||t.message||`Error ${e.status}`);E.toast(t.message||"Action completed.",{type:"success"})}catch(e){E.toast(e.message||"Action failed.",{type:"error"})}finally{l.disabled=!1,l.textContent="",i.forEach(e=>l.appendChild(e)),Domma.icons.scan(l)}})})}(function(){const l=document.querySelectorAll("[data-collection-table]");!l.length||typeof T>"u"||!T.create||l.forEach(C=>{let v;try{v=JSON.parse(atob(C.dataset.payload))}catch{return}const{columns:b,rows:p,search:i,sortable:d,exportable:e,pageSize:t,empty:o,ctaConfig:s}=v;if(!b?.length)return;if(s){const m=b.findIndex(f=>f.key==="_cta");m!==-1&&(b[m]={key:"_cta",title:"",render:(f,n)=>{const a=document.createElement("button");if(a.className=`btn btn-${s.style||"primary"} dm-cta-trigger`,a.dataset.action=s.action||"",a.dataset.entry=n._entryId||"",s.confirm&&(a.dataset.confirm=s.confirm),s.icon){const r=document.createElement("span");r.dataset.icon=s.icon,a.appendChild(r),a.appendChild(document.createTextNode(" "))}return a.appendChild(document.createTextNode(s.label||"Run")),a}})}const h="col-table-"+Math.random().toString(36).slice(2,7),g=document.createElement("div");g.id=h,C.replaceChildren(g),T.create("#"+h,{data:p,columns:b,search:i,sortable:d,exportable:e,pageSize:t,emptyMessage:o}),s&&wireCTAButtons(g)})})(),(function(){const l=document.querySelectorAll("[data-form-inline]");if(!l.length||typeof F>"u")return;function C(i,d){const e={};return(i||[]).forEach(t=>{if(t.type==="page-break"||t.type==="spacer"||!t.name)return;const o=t.type==="checkbox"?"boolean":t.type==="date"?"string":t.type,s={...t.formConfig||{}};s.span==="full"&&d&&(s.span=d),e[t.name]={type:o,label:t.label,required:t.required,options:t.options,formConfig:{...t.placeholder&&{placeholder:t.placeholder},...t.helper&&{hint:t.helper},...s}}}),e}function v(i){const d={};return(i||[]).forEach(e=>{if(!(!e.name||e.type==="page-break"||e.type==="spacer")&&(e.type==="select"||e.type==="multiselect")&&e.required){const t=(e.options||[])[0];t&&(d[e.name]=typeof t=="object"?t.value:t)}}),d}function b(i,d){(d||[]).forEach(e=>{if(e.type!=="date"||!e.name)return;const t=i.querySelector(`[name="${e.name}"]`);t&&t.type!=="date"&&(t.type="date")})}function p(i,d,e){let t=i.querySelector(".cms-form-message");t||(t=document.createElement("p"),t.className="cms-form-message",i.appendChild(t)),t.textContent=d,t.style.cssText=e?"color:var(--danger,#f87171);margin-top:.75rem;":"color:var(--success,#4ade80);margin-top:.75rem;"}l.forEach(i=>{let d;try{d=JSON.parse(atob(i.dataset.formInline))}catch{return}const e=d.fields||[],t=d.settings||{},o=t.columns||1,s=t.layout||"stacked",h=e.some(n=>n.type==="page-break"),g=async n=>{try{const a=i.querySelector('[name="website"]'),r=i.querySelector('[name="_t"]'),c=Object.assign({},n);a!==null&&(c._hp=a.value),r!==null&&(c._t=r.value);const u=await H.post(`/api/forms/submit/${d.slug}`,c);if(u?.redirect){window.location.href=u.redirect;return}for(;i.firstChild;)i.removeChild(i.firstChild);p(i,u?.message||t.successMessage||"Thank you for your submission.",!1)}catch(a){throw p(i,a.message||"Submission failed. Please try again.",!0),a}};function m(n){const a=n.querySelector("form");if(!a)return;const r=document.createElement("div");r.className="fb-form-honeypot",r.setAttribute("aria-hidden","true");const c=document.createElement("input");c.name="website",c.type="text",c.tabIndex=-1,c.autocomplete="url",c.placeholder="https://",r.appendChild(c);const u=document.createElement("input");u.name="_t",u.type="hidden",u.value=Date.now(),r.appendChild(u),a.appendChild(r)}function f(){if(!window.FormLogicEngine||!e.some(a=>a.logic))return;const n=new window.FormLogicEngine.FormLogicRuntime(d,i);if(n.init(),i._formLogicRuntime=n,i.parentNode&&typeof MutationObserver<"u"){const a=new MutationObserver(function(r){for(const c of r)for(const u of c.removedNodes)if(u===i||u.nodeType===1&&u.contains&&u.contains(i)){n.destroy(),a.disconnect();return}});a.observe(i.parentNode,{childList:!0,subtree:!1})}}if(h&&F.wizard){const n=[];let a=[],r=d.title||"Step 1",c="";e.forEach(y=>{y.type==="page-break"?(n.push({title:r,description:c,fields:C(a,o)}),a=[],r=y.label||`Step ${n.length+1}`,c=y.description||""):y.type!=="spacer"&&a.push(y)}),n.push({title:r,description:c,fields:C(a,o)});const u=F.wizard(i,{schema:{steps:n},onSubmit:g});Promise.resolve(u).then(function(){b(i,e),t.honeypot!==!1&&m(i),f()})}else if(F.render){const n=F.render(i,C(e,o),v(e),{submitText:t.submitText||"Submit",layout:s,columns:o,onSubmit:g});Promise.resolve(n).then(function(){if(s==="grid"&&t.submitSpan==="full"){const a=i.querySelector(".form-buttons");a&&a.classList.add("col-span-full")}b(i,e),t.honeypot!==!1&&m(i),f()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
@@ -202,7 +202,8 @@ function buildFormFromCollection(schema) {
202
202
  label: f.label || f.name,
203
203
  required: !!f.required,
204
204
  placeholder: f.placeholder || '',
205
- helper: f.helper || ''
205
+ helper: f.helper || '',
206
+ tooltip: f.tooltip || ''
206
207
  };
207
208
  if (f.options) field.options = f.options;
208
209
  if (f.validation) field.validation = f.validation;
@@ -22,7 +22,7 @@ const BUILTIN_SHORTCODES = new Set([
22
22
  'text', 'button', 'link', 'cta', 'grid', 'row', 'col', 'card',
23
23
  'banner', 'slideover', 'counter', 'celebrate', 'firework', 'fireworks', 'scribe',
24
24
  'reveal', 'breathe', 'pulse', 'shake', 'scramble', 'ripple', 'twinkle',
25
- 'animate', 'ambient', 'list-group',
25
+ 'ticker-tape', 'animate', 'ambient', 'list-group',
26
26
  ]);
27
27
 
28
28
  // Configure marked for safe output
@@ -790,7 +790,7 @@ function processPluginShortcodes(markdown) {
790
790
  * Process built-in Effects shortcodes natively.
791
791
  * Handles: [counter /], [celebrate /], [firework], [fireworks], [scribe],
792
792
  * [reveal], [breathe], [pulse], [shake], [scramble], [ripple],
793
- * [twinkle], [animate], [ambient]
793
+ * [twinkle], [ticker-tape], [animate], [ambient]
794
794
  *
795
795
  * @param {string} markdown
796
796
  * @returns {string}
@@ -902,6 +902,20 @@ function processEffectsBlocks(markdown) {
902
902
  });
903
903
  }
904
904
 
905
+ // [ticker-tape /] → full-page overlay (mode=page)
906
+ // [ticker-tape] body [/ticker-tape] → container-scoped (mode=container)
907
+ apply('ticker-tape', (attrStr, body) => {
908
+ const attrs = parseShortcodeAttrs(attrStr);
909
+ const dataAttrs = Object.entries(attrs)
910
+ .map(([k, v]) => ` data-fx-${k}="${escapeAttr(String(v))}"`)
911
+ .join('');
912
+ if (body === null) {
913
+ return `<div class="dm-fx-ticker-tape" data-fx-mode="page"${dataAttrs}></div>`;
914
+ }
915
+ const innerHtml = marked.parse(processCardBlocks(processGridBlocks(body.trim())));
916
+ return `<div class="dm-fx-ticker-tape" data-fx-mode="container"${dataAttrs}>${innerHtml}</div>\n`;
917
+ });
918
+
905
919
  apply('animate', (attrStr, body) => {
906
920
  if (body === null) return '';
907
921
  const attrs = parseShortcodeAttrs(attrStr);
@@ -975,14 +989,18 @@ function processGridBlocks(markdown) {
975
989
  /\[col([^\]]*)\]([\s\S]*?)\[\/col\]/gi,
976
990
  (_, attrStr, body) => {
977
991
  const attrs = parseShortcodeAttrs(attrStr);
978
- const cls = attrs.span ? ` class="col-span-${attrs.span}"` : ' class="col"';
992
+ const COL_INJECTABLE = ['reveal', 'breathe', 'animate', 'ripple'];
993
+ const injected = extractInjectedEffects(attrs, COL_INJECTABLE);
994
+ const baseCls = attrs.span ? `col-span-${attrs.span}` : 'col';
995
+ const allClasses = [baseCls, ...injected.effectClasses].join(' ');
996
+ const cls = ` class="${allClasses}"`;
979
997
  const id = attrs.id ? ` id="${escapeAttr(attrs.id)}"` : '';
980
998
  // Restore the col body before passing it downstream — otherwise the
981
999
  // scrub placeholders from this processor's store get carried into
982
1000
  // processCardBlocks, which creates its own empty store and fails to
983
1001
  // decode them, producing the literal string "undefined" in place of
984
1002
  // whatever was in <pre>, ``` fences, or `inline code`.
985
- return `<div${cls}${id}>${marked.parse(processCardBlocks(restore(body.trim())))}</div>`;
1003
+ return `<div${cls}${id}${injected.effectDataAttrs}>${marked.parse(processCardBlocks(restore(body.trim())))}</div>`;
986
1004
  }
987
1005
  );
988
1006
 
@@ -1102,6 +1120,47 @@ export function escapeAttr(str) {
1102
1120
  .replace(/>/g, '&gt;');
1103
1121
  }
1104
1122
 
1123
+ /**
1124
+ * Extract injected-effect flag attributes from a parsed shortcode attrs object.
1125
+ *
1126
+ * For each effect in `allowed` whose flag attribute is present in `attrs`,
1127
+ * emit a `dm-fx-<effect>` class and convert all `<effect>-<attr>` keys to
1128
+ * `data-fx-<attr>="<escaped-value>"` fragments. Returns the consumed keys
1129
+ * so the host processor can skip them when emitting its own attributes.
1130
+ *
1131
+ * The flag attribute is presence-only (parseShortcodeAttrs stores bare flags
1132
+ * as `true`); only prefixed attributes carry a meaningful value.
1133
+ *
1134
+ * @param {Object<string,string>} attrs - parsed shortcode attribute map
1135
+ * @param {string[]} allowed - effect names this host accepts (e.g. ['reveal','pulse'])
1136
+ * @returns {{effectClasses: string[], effectDataAttrs: string, consumedKeys: Set<string>}}
1137
+ */
1138
+ export function extractInjectedEffects(attrs, allowed) {
1139
+ const effectClasses = [];
1140
+ const dataAttrParts = [];
1141
+ const consumedKeys = new Set();
1142
+
1143
+ for (const effect of allowed) {
1144
+ if (!(effect in attrs)) continue;
1145
+ effectClasses.push(`dm-fx-${effect}`);
1146
+ consumedKeys.add(effect);
1147
+
1148
+ const prefix = `${effect}-`;
1149
+ for (const key of Object.keys(attrs)) {
1150
+ if (!key.startsWith(prefix)) continue;
1151
+ const param = key.slice(prefix.length);
1152
+ dataAttrParts.push(` data-fx-${param}="${escapeAttr(String(attrs[key]))}"`);
1153
+ consumedKeys.add(key);
1154
+ }
1155
+ }
1156
+
1157
+ return {
1158
+ effectClasses,
1159
+ effectDataAttrs: dataAttrParts.join(''),
1160
+ consumedKeys,
1161
+ };
1162
+ }
1163
+
1105
1164
  // ---------------------------------------------------------------------------
1106
1165
  // Card shared helpers
1107
1166
  // ---------------------------------------------------------------------------
@@ -1141,6 +1200,10 @@ function cardVariantClasses(attrs) {
1141
1200
  return classes;
1142
1201
  }
1143
1202
 
1203
+ // Effects that may be injected as flag-attributes onto [card] hosts.
1204
+ // Used by both cardRoot (LAYOUT_RENDERERS path) and renderLegacyCard (fallback).
1205
+ const CARD_INJECTABLE = ['reveal', 'breathe', 'pulse', 'shake', 'twinkle', 'ambient', 'animate', 'ripple'];
1206
+
1144
1207
  /**
1145
1208
  * Builds the opening `<div>` tag for a card root element.
1146
1209
  *
@@ -1149,10 +1212,11 @@ function cardVariantClasses(attrs) {
1149
1212
  * @returns {string}
1150
1213
  */
1151
1214
  function cardRoot(attrs, extraClasses = []) {
1152
- const classes = [...cardVariantClasses(attrs), ...extraClasses];
1215
+ const injected = extractInjectedEffects(attrs, CARD_INJECTABLE);
1216
+ const classes = [...cardVariantClasses(attrs), ...extraClasses, ...injected.effectClasses];
1153
1217
  const id = attrs.id ? ` id="${escapeAttr(attrs.id)}"` : '';
1154
1218
  const coll = attrs.collapsible === 'true' ? ' data-collapsible="true"' : '';
1155
- return `<div class="${classes.join(' ')}"${id}${coll}>`;
1219
+ return `<div class="${classes.join(' ')}"${id}${coll}${injected.effectDataAttrs}>`;
1156
1220
  }
1157
1221
 
1158
1222
  /**
@@ -1630,12 +1694,15 @@ function renderLegacyCard(attrs, body, markedInstance, escAttr) {
1630
1694
  const collapsible = attrs.collapsible === 'true';
1631
1695
  const variant = strAttr('variant');
1632
1696
 
1697
+ const injected = extractInjectedEffects(attrs, CARD_INJECTABLE);
1698
+
1633
1699
  // Root class list — delegate to the shared helper so variant="gradient",
1634
1700
  // gradient="<name>", glass/accent/dark/glow, font, shadow, rounded, etc.
1635
1701
  // all work on legacy cards (cards without a `layout` attribute).
1636
1702
  const classes = cardVariantClasses(attrs);
1637
1703
  // Preserve legacy support for variant="primary" (not handled by cardVariantClasses).
1638
1704
  if (variant === 'primary') classes.push('card-primary');
1705
+ classes.push(...injected.effectClasses);
1639
1706
 
1640
1707
  const id = attrs.id ? ` id="${escAttr(attrs.id)}"` : '';
1641
1708
  const coll = collapsible ? ' data-collapsible="true"' : '';
@@ -1692,7 +1759,7 @@ function renderLegacyCard(attrs, body, markedInstance, escAttr) {
1692
1759
  ? `<div class="card-footer">${markedInstance.parse(footerContent)}</div>`
1693
1760
  : footer ? `<div class="card-footer">${footer}</div>` : '';
1694
1761
 
1695
- return `<div class="${classes.join(' ')}"${coll}${id}>${headerHtml}${bodyHtml}${footerHtml}</div>\n`;
1762
+ return `<div class="${classes.join(' ')}"${coll}${id}${injected.effectDataAttrs}>${headerHtml}${bodyHtml}${footerHtml}</div>\n`;
1696
1763
  }
1697
1764
 
1698
1765
  // ---------------------------------------------------------------------------
@@ -2234,6 +2301,8 @@ function processButtonBlocks(markdown) {
2234
2301
 
2235
2302
  function buildButton(attrStr, inner) {
2236
2303
  const attrs = parseShortcodeAttrs(attrStr || '');
2304
+ const BUTTON_INJECTABLE = ['breathe', 'pulse', 'shake', 'animate', 'ripple'];
2305
+ const injected = extractInjectedEffects(attrs, BUTTON_INJECTABLE);
2237
2306
  const href = attrs.href || '#';
2238
2307
  const label = inner !== null ? inner.trim() : (attrs.label || '');
2239
2308
  const variant = VALID_VARIANTS.has(attrs.variant) ? attrs.variant : 'primary';
@@ -2241,11 +2310,12 @@ function processButtonBlocks(markdown) {
2241
2310
  if (attrs.size === 'sm') classes.push('btn-sm');
2242
2311
  if (attrs.size === 'lg') classes.push('btn-lg');
2243
2312
  if (attrs.class) classes.push(escapeAttr(attrs.class));
2313
+ classes.push(...injected.effectClasses);
2244
2314
  const idAttr = attrs.id ? ` id="${escapeAttr(attrs.id)}"` : '';
2245
2315
  const targetAttr = attrs.target ? ` target="${escapeAttr(attrs.target)}"` : '';
2246
2316
  const icon = attrs.icon ? `<span data-icon="${escapeAttr(attrs.icon)}"></span> ` : '';
2247
2317
  const iconAfter = attrs['icon-after'] ? ` <span data-icon="${escapeAttr(attrs['icon-after'])}"></span>` : '';
2248
- return `<a href="${escapeAttr(href)}" class="${classes.join(' ')}"${idAttr}${targetAttr}>${icon}${label}${iconAfter}</a>`;
2318
+ return `<a href="${escapeAttr(href)}" class="${classes.join(' ')}"${idAttr}${targetAttr}${injected.effectDataAttrs}>${icon}${label}${iconAfter}</a>`;
2249
2319
  }
2250
2320
 
2251
2321
  let result = scrubbed.replace(/\[button([^\]]*?)\/\]/gi, (_, attrStr) => buildButton(attrStr, null));
@@ -2351,8 +2421,7 @@ function processTableBlocks(markdown) {
2351
2421
  * fullwidth - "true" breaks out of the page container to span the full viewport width (adds .hero-breakout).
2352
2422
  * Must be the literal string "true" — the attribute is otherwise ignored.
2353
2423
  * twinkle - Flag attribute — adds particle overlay (requires Effects plugin)
2354
- * twinkle-count - Number of twinkle particles
2355
- * twinkle-colour - Particle colour (CSS value)
2424
+ * twinkle-* - Any prefixed attribute is forwarded as data-fx-* (see extractInjectedEffects)
2356
2425
  * blobs - Flag attribute — adds ambient blob background
2357
2426
  * blobs-type - Blob animation type (default: "float-blobs")
2358
2427
  * class - Extra classes appended to .hero
@@ -2589,9 +2658,11 @@ function processHeroBlocks(markdown) {
2589
2658
  const heroColor = attrs.color ? String(attrs.color) : '';
2590
2659
  const minHeight = attrs['min-height'] ? String(attrs['min-height']) : '';
2591
2660
 
2592
- const twinkle = 'twinkle' in attrs;
2593
- const twinkleCount = attrs['twinkle-count'] || '';
2594
- const twinkleColour = attrs['twinkle-colour'] || '';
2661
+ const HERO_INJECTABLE = ['reveal', 'breathe', 'pulse', 'twinkle', 'ticker-tape', 'ambient', 'animate'];
2662
+ const injected = extractInjectedEffects(attrs, HERO_INJECTABLE);
2663
+
2664
+ // Legacy ad-hoc 'blobs' flag — keep for backward compat. Different keyspace
2665
+ // (bg-ambient-* CSS class), not folded into the generic helper.
2595
2666
  const blobs = 'blobs' in attrs;
2596
2667
  const blobsType = attrs['blobs-type'] || 'float-blobs';
2597
2668
 
@@ -2603,7 +2674,7 @@ function processHeroBlocks(markdown) {
2603
2674
  if (overlay) classes.push(`hero-overlay-${overlay}`);
2604
2675
  if (fullwidth) classes.push('hero-breakout');
2605
2676
  if (cls) classes.push(cls);
2606
- if (twinkle) classes.push('dm-fx-twinkle');
2677
+ classes.push(...injected.effectClasses);
2607
2678
  if (blobs) classes.push(`bg-ambient-${blobsType}`);
2608
2679
 
2609
2680
  const styleParts = [];
@@ -2616,11 +2687,6 @@ function processHeroBlocks(markdown) {
2616
2687
  const style = styleParts.length ? ` style="${styleParts.join(';')}"` : '';
2617
2688
  const idAttr = id ? ` id="${escapeAttr(id)}"` : '';
2618
2689
 
2619
- const twinkleAttrs = twinkle
2620
- ? (twinkleCount ? ` data-fx-count="${escapeAttr(twinkleCount)}"` : '') +
2621
- (twinkleColour ? ` data-fx-colour="${escapeAttr(twinkleColour)}"` : '')
2622
- : '';
2623
-
2624
2690
  // Button/link/cta must run before marked.parse so their quoted attributes
2625
2691
  // aren't HTML-escaped to &quot; — that mangles parseShortcodeAttrs and
2626
2692
  // turns `href="/x"` into a bare flag (attrs.href = true → href="true").
@@ -2634,7 +2700,7 @@ function processHeroBlocks(markdown) {
2634
2700
  if (processedBody) inner += `<div class="hero-body">${marked.parse(processedBody)}</div>`;
2635
2701
  inner += '</div>';
2636
2702
 
2637
- return `<div class="${classes.join(' ')}"${idAttr}${style}${twinkleAttrs}>${inner}</div>\n`;
2703
+ return `<div class="${classes.join(' ')}"${idAttr}${style}${injected.effectDataAttrs}>${inner}</div>\n`;
2638
2704
  }
2639
2705
  );
2640
2706
  return restore(result);
@@ -2678,6 +2744,8 @@ function processBannerBlocks(markdown) {
2678
2744
  /\[banner([^\]]*)\]([\s\S]*?)\[\/banner\]/gi,
2679
2745
  (_, attrStr, body) => {
2680
2746
  const attrs = parseShortcodeAttrs(attrStr);
2747
+ const BANNER_INJECTABLE = ['reveal', 'pulse', 'shake', 'ambient', 'animate'];
2748
+ const injected = extractInjectedEffects(attrs, BANNER_INJECTABLE);
2681
2749
  const VALID_BANNER_TYPES = new Set(['info', 'success', 'warning', 'danger', 'neutral']);
2682
2750
  const type = VALID_BANNER_TYPES.has(attrs.type) ? attrs.type : 'info';
2683
2751
  const title = attrs.title || '';
@@ -2687,6 +2755,7 @@ function processBannerBlocks(markdown) {
2687
2755
 
2688
2756
  const classes = [`dm-banner`, `dm-banner--${type}`];
2689
2757
  if (extraClass) classes.push(extraClass);
2758
+ classes.push(...injected.effectClasses);
2690
2759
 
2691
2760
  const iconHtml = icon
2692
2761
  ? `<span class="dm-banner__icon" data-icon="${escapeAttr(icon)}"></span>\n `
@@ -2700,7 +2769,7 @@ function processBannerBlocks(markdown) {
2700
2769
  : '';
2701
2770
 
2702
2771
  return (
2703
- `<div class="${classes.join(' ')}">\n` +
2772
+ `<div class="${classes.join(' ')}"${injected.effectDataAttrs}>\n` +
2704
2773
  ` ${iconHtml}<div class="dm-banner__body">\n` +
2705
2774
  ` ${titleHtml}${bodyHtml}` +
2706
2775
  ` </div>${dismissHtml}\n` +