domma-cms 1.10.17 → 1.10.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.10.18] - 2026-10-25
4
+
5
+ - **A form's files are checked against the per-submission size cap in the browser.** A form with a file field now tells the page `uploads.maxSubmissionBytes`, so a file that would take the form's files past it is refused as it is dropped or picked ("… the files together would be more than 50 MB"), counting files kept on earlier wizard steps, and the total is checked again before posting - in the `[form]` shortcode and embedded forms alike. The server still enforces it.
6
+
3
7
  ## [1.10.17] - 2026-10-25
4
8
 
5
9
  - **Form file fields can take several files.** "Several files" and "Most files" in the form editor (`multiple`, `file.maxFiles`, default 5, at most 20). Every file is kept - stored as a list of `{url, name, size, mime}`; a single-file field is unchanged - and every file passes the type and size checks. Submissions, collection entries, `[collection]` displays, the email notification and the CSV export show each file; email links are full addresses. One submission's files are capped at `uploads.maxSubmissionBytes` (default the larger of 50 MB and `uploads.maxFileSize`). Files already written are removed when a submission fails after all, and the submissions CSV guards every cell against spreadsheet formulas. A file field's value now only ever comes from an uploaded file.
@@ -29,7 +29,7 @@ Controls the HTTP server.
29
29
  | `csp.connectHosts` | string[] | `[]` | Other sites a public page may read from in the browser (`fetch`), added to the Content-Security-Policy's `connect-src` as `https://<host>`. The site itself and cdn.jsdelivr.net are always allowed. Bare hostnames only (`dcm.dommajs.org`); a scheme, path, port or wildcard is ignored. Read at boot, so restart after changing it |
30
30
  | `csp.imgHosts` | string[] | `[]` | Other sites a public page may show images from (an `<img>` hosted elsewhere, such as a badge or partner logo), added to the Content-Security-Policy's `img-src` as `https://<host>`. The site itself, `data:` and `blob:` are always allowed; without this an image from another site is blocked - upload it to Media instead if you can. Bare hostnames only (`statics.startupbase.io`); a scheme, path, port or wildcard is ignored. Read at boot, so restart after changing it |
31
31
  | `uploads.maxFileSize` | number | `10485760` (10 MB) | Maximum allowed upload size in bytes |
32
- | `uploads.maxSubmissionBytes` | number | 50 MB, or `uploads.maxFileSize` when that is larger | The most a public form submission's files may add up to, all file fields together. The server holds a submission's files in memory until every check has passed, so it stops reading and refuses the submission (`400`, nothing written) the moment the running total passes this. Read fresh on every submission |
32
+ | `uploads.maxSubmissionBytes` | number | 50 MB, or `uploads.maxFileSize` when that is larger | The most a public form submission's files may add up to, all file fields together. The server holds a submission's files in memory until every check has passed, so it stops reading and refuses the submission (`400`, nothing written) the moment the running total passes this. Read fresh on every submission. A form with a file field is sent to the page with `fileLimits.maxTotalBytes`, so the page refuses a file that would pass it as it is added, and checks again before posting. A page with a `[form]` that is already in the response cache keeps the cap it was drawn with until the site restarts or the form is saved |
33
33
  | `logging.level` | string | Log verbosity: `fatal`, `error`, `warn`, `info`, `debug`, `trace`, `silent`. Defaults to `info` in development, `warn` otherwise. `warn` hides 4xx responses and plugin `log.info` output, so a failed admin action leaves no trace - raise it to `info` if you need to diagnose one. Overridden by the `LOG_LEVEL` env var. |
34
34
  | `restartOnPluginToggle` | boolean | `true` | Restart the server automatically when an admin enables or disables a plugin. See **Plugin toggles restart the server** below |
35
35
  | `requireSignedPlugins` | boolean | `false` | When `true`, the site runs only plugin code the Marketplace signed: an unsigned `.dcmsplugin` (e.g. one exported from another site) is refused even if the installer confirms it, New plugin is refused, a plugin's files cannot be changed in the code editor (it opens read only), licensed source cannot be read even by a Super Admin, and MongoDB connection options that start a program or read a file (`autoEncryption`, `tls…File`, …) cannot be saved. A bundle with a signature that does not verify is always refused, whatever this is set to. Read fresh on every check, and also as it was at boot, so changing the file on a running site can switch it on but not off. `DOMMA_SIGNED_ONLY=1` in the environment turns it on whatever the file says - Domma CMS Manager sets it on the sites it hosts. Nothing in the admin writes `server.json`. See `server/services/codePolicy.js` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "1.10.17",
3
+ "version": "1.10.18",
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
- export const DEFAULT_MAX_SIZE=5242880,DEFAULT_MAX_FILES=5,MAX_FILES_LIMIT=20;export function fileRules(e){const t=e&&e.file||{},n=!!(e&&e.multiple),i=Math.floor(Number(t.maxFiles));return{accept:String(t.accept||"").split(",").map(o=>o.trim()).filter(Boolean),maxSize:Number(t.maxSize)||5242880,multiple:n,maxFiles:n?Math.min(i>=1?i:5,20):1}}function c(e){return e===1?"one file":`${e} files`}export function countRefusal(e,t){const{maxFiles:n,multiple:i}=fileRules(e);if(Number(t)<=n)return null;const o=e&&(e.label||e.name)||"File";return i?`"${o}" takes at most ${c(n)}.`:`"${o}" takes one file.`}export function fileRefs(e){return(Array.isArray(e)?e:[e]).filter(n=>n&&typeof n=="object"&&!Array.isArray(n)&&typeof n.url=="string"&&(typeof n.name=="string"||n.name===void 0))}export function isFileValue(e){const t=fileRefs(e);return t.length>0&&t.length===(Array.isArray(e)?e.length:1)}export function fileValueText(e,{base:t="",urls:n=!0}={}){return fileRefs(e).map(i=>{const o=i.name||i.url.split("/").pop()||"file";return!n||!i.url?o:`${o} (${fileUrl(i,t)})`}).join("; ")}export function fileUrl(e,t=""){const n=String(e&&e.url||""),i=String(t||"").replace(/\/+$/,"");return i&&n.startsWith("/")?`${i}${n}`:n}export function mimeAccepted(e,t){if(!e||!e.length)return!0;const n=String(t||"");return e.some(i=>i.endsWith("/*")?n.startsWith(i.slice(0,-1)):i===n)}export function cleanUploadName(e){const t=n=>n.replace(/^[-.]+|[-.]+$/g,"");return t(t(String(e||"upload").toLowerCase().replace(/[^\w.-]+/g,"-").replace(/-+/g,"-")).slice(0,200))||"upload"}const p=/^(text\/html|text\/xml|text\/xsl|text\/javascript|text\/ecmascript|application\/(x-)?javascript|application\/ecmascript|application\/xml|application\/[\w.-]+\+xml|image\/svg\+xml)\b/i,m=/\.(html?|xht(ml)?|shtml?|svgz?|xml|xsd|xslt?|xul|rdf|owl|atom|rss|wsdl|smil?|kml|opf|xlf|gpx|ttml|mathml|mml|xop|m?js|mht(ml)?)$/i;function d(e){const t=/\.[^.]+$/.exec(cleanUploadName(e&&(e.filename??e.name)||""));return t?t[0]:""}export function isActiveFile(e){const t=String(e&&(e.mimetype??e.type)||""),n=e&&(e.filename??e.name)||"";return p.test(t)||m.test(cleanUploadName(n))}export function fileRefusal(e,t){const n=fileRules(e),i=e&&(e.label||e.name)||"File";if(Number(t&&t.size)>n.maxSize)return`"${i}" file is too large (max ${Math.round(n.maxSize/1024)} KB).`;const o=t&&(t.mimetype??t.type)||"";return isActiveFile(t)?`"${i}" file type "${o||d(t)}" not allowed (it could run as a page on this site).`:n.accept.length&&!mimeAccepted(n.accept,o)?`"${i}" file type "${o}" not allowed (expected: ${String(e.file.accept).trim()}).`:null}const x=[[/wordprocessingml|^msword$/,"Word"],[/spreadsheetml|ms-excel/,"Excel"],[/presentationml|ms-powerpoint/,"PowerPoint"],[/^plain$/,"text"]];export function describeAccept(e){const t=[];return(e||[]).forEach(n=>{const[i,o]=String(n).split("/");let r;o==="*"?r={image:"images",audio:"audio",video:"video",text:"text files"}[i]||`${i} files`:o?r=x.find(([s])=>s.test(o))?.[1]||o.replace(/^(x-|vnd\.)/,"").split(/[.+]/)[0].toUpperCase():r=String(n),r&&!t.includes(r)&&t.push(r)}),t.length<2?t[0]||"":`${t.slice(0,-1).join(", ")} or ${t[t.length-1]}`}export function formatSize(e){const t=Number(e)||0;if(t<1024)return`${t} B`;const n=["KB","MB","GB"];let i=t,o=-1;do i/=1024,o++;while(i>=1024&&o<n.length-1);return`${i>=10?Math.round(i):Math.round(i*10)/10} ${n[o]}`}export function fileHint(e){const t=fileRules(e),n=describeAccept(t.accept),i=t.multiple?`up to ${c(t.maxFiles)}, ${formatSize(t.maxSize)} each`:`up to ${formatSize(t.maxSize)}`;return n?`${n[0].toUpperCase()}${n.slice(1)} - ${i}`:`Any file - ${i}`}export function dropzoneOptions(e){const t=fileRules(e);return{accept:t.accept.length?t.accept.join(","):null,maxSize:t.maxSize,multiple:t.multiple,maxFiles:t.multiple?t.maxFiles:null,labels:{prompt:t.multiple?"Drop files here, paste, or click to browse":"Drop a file here, paste, or click to browse",hint:fileHint(e),required:"Please add a file",type:refusalMessage(e,{name:"{name}"},"type"),size:refusalMessage(e,{name:"{name}"},"size"),count:refusalMessage(e,{name:"{name}"},"count")}}}export function fileBlueprint(e,t){return{...t,type:"dropzone",formConfig:{...t.formConfig||{},...dropzoneOptions(e)}}}export function refusalMessage(e,t,n){const i=fileRules(e),o=t&&t.name||"That file";if(n==="size")return`${o} was not added: it is larger than ${formatSize(i.maxSize)}.`;if(n==="type"){const r=describeAccept(i.accept);return`${o} was not added: ${r?`only ${r} can be uploaded here`:"that type of file is not accepted"}.`}return n==="active"?`${o} was not added: web pages, SVG images and scripts cannot be uploaded.`:`${o} was not added: only ${c(i.maxFiles)} can be added here.`}function h(e){return Array.isArray(e)&&e.length>0&&typeof File<"u"&&e.every(t=>t instanceof File)}export function splitFiles(e,t,n){const i={...e||{}},o=new Map,r=new Set((t||[]).filter(s=>s&&s.type==="file"&&s.name).map(s=>s.name));for(const[s,l]of Object.entries(i))h(l)?(o.set(s,l),delete i[s]):r.has(s)&&(Array.isArray(l)||l==null)&&(i[s]="");for(const[s,l]of n||[])o.set(s,l||[]);for(const[s,l]of o)l.length?delete i[s]:(o.delete(s),(r.has(s)||s in i)&&(i[s]=""));return{values:i,files:o}}export function submitBody(e,t){if(![...(t||new Map).values()].some(o=>o&&o.length))return{body:JSON.stringify(e),headers:{"Content-Type":"application/json"},multipart:!1};const i=new FormData;for(const[o,r]of Object.entries(e))r==null||t.has(o)||i.append(o,typeof r=="object"?JSON.stringify(r):String(r));for(const[o,r]of t)for(const s of r||[])i.append(o,s,s.name);return{body:i,headers:{},multipart:!0}}export function captureFileFields(e,t){!e||!t||e.querySelectorAll('input[type="file"]').forEach(n=>{n.name&&t.set(n.name,Array.from(n.files||[]))})}export function restoreFileFields(e,t){!e||!t||!t.size||e.querySelectorAll('input[type="file"]').forEach(n=>{const i=t.get(n.name);if(!i||!i.length||n.files&&n.files.length)return;const o=f(n);if(o){o.addFiles(i);return}try{const r=new DataTransfer;i.forEach(s=>r.items.add(s)),n.files=r.files}catch{}})}function f(e){const t=globalThis.E;if(!t||typeof t.get!="function")return null;const n=e.closest("[data-dropzone-field]"),i=n&&t.get(n,"dropzone")||t.get(e,"dropzone");return i&&typeof i.addFiles=="function"?i:null}function u(e,t){const n=String(t&&t.name||"").replace(/\[\]$/,"");return(e||[]).find(i=>i&&i.name===n)||{name:n}}function g(e){let t=e.nextElementSibling;return(!t||!t.classList.contains("fb-file-refusal"))&&(t=document.createElement("p"),t.className="fb-file-refusal",t.hidden=!0,e.after(t)),t}export function wireFileFields(e,t){if(!e||e._fbFilesWired)return;e._fbFilesWired=!0;const n=new WeakMap,i=(o,r,s)=>{const l=g(o.zone),a=n.get(o.zone)||[];a.push(refusalMessage(u(t,o.input),r,s)),n.set(o.zone,a),l.textContent=a.join(" "),l.hidden=!1,o.zone.classList.add("is-invalid"),queueMicrotask(()=>n.delete(o.zone))};e.addEventListener("dm:dropzone:reject",o=>{const r=o.detail&&o.detail.instance,[s,l]=o.detail&&o.detail.args||[];r&&r.zone&&i(r,s,l)}),e.addEventListener("dm:dropzone:beforeadd",o=>{const r=o.detail&&o.detail.instance,[s]=o.detail&&o.detail.args||[];!r||!r.zone||!s||!isActiveFile(s.file||s)||(o.preventDefault(),i(r,s,"active"))}),e.addEventListener("dm:dropzone:add",o=>{const r=o.detail&&o.detail.instance;if(!r||!r.zone||n.get(r.zone))return;const s=r.zone.nextElementSibling;s&&s.classList.contains("fb-file-refusal")&&(s.hidden=!0,s.textContent="")}),e.addEventListener("dm:dropzone:change",o=>{const r=o.detail&&o.detail.instance;if(!r||!r.input)return;r.input.dispatchEvent(new Event("input",{bubbles:!0}));const s=r.input.closest(".fb-field-invalid");s&&r.getFiles().length&&s.classList.remove("fb-field-invalid")})}export function enhanceFileInputs(e,t){const n=globalThis.E;!e||!n||typeof n.dropzone!="function"||(e.querySelectorAll('input[type="file"]').forEach(i=>{f(i)||n.dropzone(i,dropzoneOptions(u(t,i)))}),wireFileFields(e,t))}export function checkFileInputs(e,t){const n=[];return e&&e.querySelectorAll('input[type="file"]').forEach(i=>{const o=u(t,i),r=Array.from(i.files||[]),s=countRefusal(o,r.length);s&&n.push(s),r.forEach(l=>{const a=fileRefusal(o,l);a&&n.push(a)})}),n}export function destroyDropzones(e){const t=globalThis.E;!e||!t||typeof t.get!="function"||e.querySelectorAll('.dm-dropzone, input[type="file"]').forEach(n=>{const i=t.get(n,"dropzone");i&&typeof i.destroy=="function"&&i.destroy()})}
1
+ export const DEFAULT_MAX_SIZE=5242880,DEFAULT_MAX_FILES=5,MAX_FILES_LIMIT=20;export function fileRules(e){const t=e&&e.file||{},n=!!(e&&e.multiple),o=Math.floor(Number(t.maxFiles));return{accept:String(t.accept||"").split(",").map(r=>r.trim()).filter(Boolean),maxSize:Number(t.maxSize)||5242880,multiple:n,maxFiles:n?Math.min(o>=1?o:5,20):1}}function d(e){return e===1?"one file":`${e} files`}export function countRefusal(e,t){const{maxFiles:n,multiple:o}=fileRules(e);if(Number(t)<=n)return null;const r=e&&(e.label||e.name)||"File";return o?`"${r}" takes at most ${d(n)}.`:`"${r}" takes one file.`}export function fileRefs(e){return(Array.isArray(e)?e:[e]).filter(n=>n&&typeof n=="object"&&!Array.isArray(n)&&typeof n.url=="string"&&(typeof n.name=="string"||n.name===void 0))}export function isFileValue(e){const t=fileRefs(e);return t.length>0&&t.length===(Array.isArray(e)?e.length:1)}export function fileValueText(e,{base:t="",urls:n=!0}={}){return fileRefs(e).map(o=>{const r=o.name||o.url.split("/").pop()||"file";return!n||!o.url?r:`${r} (${fileUrl(o,t)})`}).join("; ")}export function fileUrl(e,t=""){const n=String(e&&e.url||""),o=String(t||"").replace(/\/+$/,"");return o&&n.startsWith("/")?`${o}${n}`:n}export function mimeAccepted(e,t){if(!e||!e.length)return!0;const n=String(t||"");return e.some(o=>o.endsWith("/*")?n.startsWith(o.slice(0,-1)):o===n)}export function cleanUploadName(e){const t=n=>n.replace(/^[-.]+|[-.]+$/g,"");return t(t(String(e||"upload").toLowerCase().replace(/[^\w.-]+/g,"-").replace(/-+/g,"-")).slice(0,200))||"upload"}const y=/^(text\/html|text\/xml|text\/xsl|text\/javascript|text\/ecmascript|application\/(x-)?javascript|application\/ecmascript|application\/xml|application\/[\w.-]+\+xml|image\/svg\+xml)\b/i,b=/\.(html?|xht(ml)?|shtml?|svgz?|xml|xsd|xslt?|xul|rdf|owl|atom|rss|wsdl|smil?|kml|opf|xlf|gpx|ttml|mathml|mml|xop|m?js|mht(ml)?)$/i;function z(e){const t=/\.[^.]+$/.exec(cleanUploadName(e&&(e.filename??e.name)||""));return t?t[0]:""}export function isActiveFile(e){const t=String(e&&(e.mimetype??e.type)||""),n=e&&(e.filename??e.name)||"";return y.test(t)||b.test(cleanUploadName(n))}export function fileRefusal(e,t){const n=fileRules(e),o=e&&(e.label||e.name)||"File";if(Number(t&&t.size)>n.maxSize)return`"${o}" file is too large (max ${Math.round(n.maxSize/1024)} KB).`;const r=t&&(t.mimetype??t.type)||"";return isActiveFile(t)?`"${o}" file type "${r||z(t)}" not allowed (it could run as a page on this site).`:n.accept.length&&!mimeAccepted(n.accept,r)?`"${o}" file type "${r}" not allowed (expected: ${String(e.file.accept).trim()}).`:null}const $=[[/wordprocessingml|^msword$/,"Word"],[/spreadsheetml|ms-excel/,"Excel"],[/presentationml|ms-powerpoint/,"PowerPoint"],[/^plain$/,"text"]];export function describeAccept(e){const t=[];return(e||[]).forEach(n=>{const[o,r]=String(n).split("/");let s;r==="*"?s={image:"images",audio:"audio",video:"video",text:"text files"}[o]||`${o} files`:r?s=$.find(([i])=>i.test(r))?.[1]||r.replace(/^(x-|vnd\.)/,"").split(/[.+]/)[0].toUpperCase():s=String(n),s&&!t.includes(s)&&t.push(s)}),t.length<2?t[0]||"":`${t.slice(0,-1).join(", ")} or ${t[t.length-1]}`}export function formatSize(e){const t=Number(e)||0;if(t<1024)return`${t} B`;const n=["KB","MB","GB"];let o=t,r=-1;do o/=1024,r++;while(o>=1024&&r<n.length-1);return`${o>=10?Math.round(o):Math.round(o*10)/10} ${n[r]}`}export function fileHint(e){const t=fileRules(e),n=describeAccept(t.accept),o=t.multiple?`up to ${d(t.maxFiles)}, ${formatSize(t.maxSize)} each`:`up to ${formatSize(t.maxSize)}`;return n?`${n[0].toUpperCase()}${n.slice(1)} - ${o}`:`Any file - ${o}`}export function dropzoneOptions(e){const t=fileRules(e);return{accept:t.accept.length?t.accept.join(","):null,maxSize:t.maxSize,multiple:t.multiple,maxFiles:t.multiple?t.maxFiles:null,labels:{prompt:t.multiple?"Drop files here, paste, or click to browse":"Drop a file here, paste, or click to browse",hint:fileHint(e),required:"Please add a file",type:refusalMessage(e,{name:"{name}"},"type"),size:refusalMessage(e,{name:"{name}"},"size"),count:refusalMessage(e,{name:"{name}"},"count")}}}export function fileBlueprint(e,t){return{...t,type:"dropzone",formConfig:{...t.formConfig||{},...dropzoneOptions(e)}}}export function refusalMessage(e,t,n,o){const r=fileRules(e),s=t&&t.name||"That file";if(n==="total")return`${s} was not added: the files together would be more than ${formatSize(o)}.`;if(n==="size")return`${s} was not added: it is larger than ${formatSize(r.maxSize)}.`;if(n==="type"){const i=describeAccept(r.accept);return`${s} was not added: ${i?`only ${i} can be uploaded here`:"that type of file is not accepted"}.`}return n==="active"?`${s} was not added: web pages, SVG images and scripts cannot be uploaded.`:`${s} was not added: only ${d(r.maxFiles)} can be added here.`}function S(e){return Array.isArray(e)&&e.length>0&&typeof File<"u"&&e.every(t=>t instanceof File)}export function splitFiles(e,t,n){const o={...e||{}},r=new Map,s=new Set((t||[]).filter(i=>i&&i.type==="file"&&i.name).map(i=>i.name));for(const[i,l]of Object.entries(o))S(l)?(r.set(i,l),delete o[i]):s.has(i)&&(Array.isArray(l)||l==null)&&(o[i]="");for(const[i,l]of n||[])r.set(i,l||[]);for(const[i,l]of r)l.length?delete o[i]:(r.delete(i),(s.has(i)||i in o)&&(o[i]=""));return{values:o,files:r}}export function submitBody(e,t){if(![...(t||new Map).values()].some(r=>r&&r.length))return{body:JSON.stringify(e),headers:{"Content-Type":"application/json"},multipart:!1};const o=new FormData;for(const[r,s]of Object.entries(e))s==null||t.has(r)||o.append(r,typeof s=="object"?JSON.stringify(s):String(s));for(const[r,s]of t)for(const i of s||[])o.append(r,i,i.name);return{body:o,headers:{},multipart:!0}}export function captureFileFields(e,t){!e||!t||e.querySelectorAll('input[type="file"]').forEach(n=>{n.name&&t.set(n.name,Array.from(n.files||[]))})}export function restoreFileFields(e,t){!e||!t||!t.size||e.querySelectorAll('input[type="file"]').forEach(n=>{const o=t.get(n.name);if(!o||!o.length||n.files&&n.files.length)return;const r=g(n);if(r){r.addFiles(o);return}try{const s=new DataTransfer;o.forEach(i=>s.items.add(i)),n.files=s.files}catch{}})}function g(e){const t=globalThis.E;if(!t||typeof t.get!="function")return null;const n=e.closest("[data-dropzone-field]"),o=n&&t.get(n,"dropzone")||t.get(e,"dropzone");return o&&typeof o.addFiles=="function"?o:null}function m(e,t){const n=String(t&&t.name||"").replace(/\[\]$/,"");return(e||[]).find(o=>o&&o.name===n)||{name:n}}function E(e){let t=e.nextElementSibling;return(!t||!t.classList.contains("fb-file-refusal"))&&(t=document.createElement("p"),t.className="fb-file-refusal",t.hidden=!0,e.after(t)),t}export function submissionTooLarge(e){const t=e/1048576;return`The files sent are too large together (at most ${t>=1?`${Math.round(t*10)/10} MB`:`${Math.round(e/1024)} KB`} in one submission). Send fewer or smaller files.`}export function setFileLimits(e,t,n){e&&(e._fbFileLimits=t||null,n&&(e._fbRetained=n))}function p(e){return Number(e&&e._fbFileLimits&&e._fbFileLimits.maxTotalBytes)||0}function f(e){let t=0;for(const n of e||[])t+=Number(n&&n.size)||0;return t}export function totalTooLarge(e,t){const n=p(e);if(!n||!t)return null;let o=0;for(const r of t.values())o+=f(r);return o>n?submissionTooLarge(n):null}function F(e,t){const n=globalThis.E,o=a=>n&&typeof n.get=="function"?n.get(a,"dropzone"):null,r=new Set;e.querySelectorAll('.dm-dropzone, [data-dropzone-field], input[type="file"]').forEach(a=>{const c=o(a);c&&r.add(c)});const s=new Set,i=new Set;let l=0;for(const a of r)a.input&&(s.add(a.input),a.input.name&&i.add(a.input.name)),a.zone&&a.zone.querySelectorAll('input[type="file"]').forEach(c=>s.add(c)),a!==t&&(l+=f(a.getFiles()));e.querySelectorAll('input[type="file"]').forEach(a=>{s.has(a)||(a.name&&i.add(a.name),l+=f(a.files))});for(const[a,c]of e._fbRetained||[])i.has(a)||(l+=f(c));return l}export function wireFileFields(e,t){if(!e||e._fbFilesWired)return;e._fbFilesWired=!0;const n=new WeakMap,o=(s,i,l)=>{const a=E(s.zone),c=n.get(s.zone)||[];c.push(refusalMessage(m(t,s.input),i,l,p(e))),n.set(s.zone,c),a.textContent=c.join(" "),a.hidden=!1,s.zone.classList.add("is-invalid"),queueMicrotask(()=>n.delete(s.zone))};e.addEventListener("dm:dropzone:reject",s=>{const i=s.detail&&s.detail.instance,[l,a]=s.detail&&s.detail.args||[];i&&i.zone&&o(i,l,a)});const r=new Map;e.addEventListener("dm:dropzone:beforeadd",s=>{const i=s.detail&&s.detail.instance,[l]=s.detail&&s.detail.args||[];if(!i||!i.zone||!l)return;const a=l.file||l;if(isActiveFile(a)){s.preventDefault(),o(i,l,"active");return}const c=p(e);if(!c)return;const u=i.options&&i.options.multiple===!1,h=Number(l.size??a.size)||0,x=u?0:r.get(i)||0;if(F(e,u?i:null)+x+h>c){s.preventDefault(),o(i,l,"total");return}u||(r.has(i)||queueMicrotask(()=>r.delete(i)),r.set(i,x+h))}),e.addEventListener("dm:dropzone:change",s=>{const i=s.detail&&s.detail.instance;i&&r.delete(i)}),e.addEventListener("dm:dropzone:add",s=>{const i=s.detail&&s.detail.instance;if(!i||!i.zone||n.get(i.zone))return;const l=i.zone.nextElementSibling;l&&l.classList.contains("fb-file-refusal")&&(l.hidden=!0,l.textContent="")}),e.addEventListener("dm:dropzone:change",s=>{const i=s.detail&&s.detail.instance;if(!i||!i.input)return;i.input.dispatchEvent(new Event("input",{bubbles:!0}));const l=i.input.closest(".fb-field-invalid");l&&i.getFiles().length&&l.classList.remove("fb-field-invalid")})}export function enhanceFileInputs(e,t){const n=globalThis.E;!e||!n||typeof n.dropzone!="function"||(e.querySelectorAll('input[type="file"]').forEach(o=>{g(o)||n.dropzone(o,dropzoneOptions(m(t,o)))}),wireFileFields(e,t))}export function checkFileInputs(e,t){const n=[];return e&&e.querySelectorAll('input[type="file"]').forEach(o=>{const r=m(t,o),s=Array.from(o.files||[]),i=countRefusal(r,s.length);i&&n.push(i),s.forEach(l=>{const a=fileRefusal(r,l);a&&n.push(a)})}),n}export function destroyDropzones(e){const t=globalThis.E;!e||!t||typeof t.get!="function"||e.querySelectorAll('.dm-dropzone, input[type="file"]').forEach(n=>{const o=t.get(n,"dropzone");o&&typeof o.destroy=="function"&&o.destroy()})}
@@ -1,2 +1,2 @@
1
- import{installSmoothCarets as H}from"/public/js/smooth-caret.js";import{buildWizardSteps as K}from"/public/js/form-steps.mjs";import{applyFormClasses as F,applySubmitButtons as _,applyWizardButtons as J,resolveSecondaryButton as Y,submitWrapperClasses as $}from"/public/js/form-submit.mjs";import{captureFileFields as L,checkFileInputs as G,destroyDropzones as Q,enhanceFileInputs as X,fileBlueprint as Z,restoreFileFields as ee,splitFiles as te,submitBody as ne,wireFileFields as j}from"/public/js/form-files.mjs";H(document);const B=document.querySelectorAll("[data-form]");B.length&&B.forEach(me);function T(t){return(t||[]).some(o=>o.logic||Array.isArray(o.triggers)&&o.triggers.length)}function oe(t,o){const n=t.querySelector('[name="'+o+'"], [name="'+o+'[]"]')||t.querySelector('[data-chooser-field="'+o+'"]');if(!n)return null;const e=n.closest("form")||t;let i=n;for(;i.parentNode&&i.parentNode!==e;)i=i.parentNode;return i}export function markInvalidFields(t,o){t.querySelectorAll(".fb-field-invalid").forEach(e=>e.classList.remove("fb-field-invalid"));let n=null;(o||[]).forEach(function(e){const i=oe(t,e.name);i&&(i.classList.add("fb-field-invalid"),n||(n=i))}),n&&n.scrollIntoView&&n.scrollIntoView({behavior:"smooth",block:"center"})}export function showValidationPopup(t){const o=(t.missing||[]).map(e=>"<strong>"+R(e.label)+"</strong> is required.").concat((t.errors||[]).map(R)),n=o.join("<br>");if(typeof E<"u"&&E.alert){E.alert(n,{title:"Please complete this step"});return}window.alert(o.join(`
2
- `).replace(/<[^>]+>/g,""))}function R(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function q(t,o){let n=!1;t.addEventListener("dm-form-end",function(e){const i=e.detail&&e.detail.validation;if(i&&!i.valid){markInvalidFields(t,i.missing),showValidationPopup(i);return}n||(n=!0,t.classList.add("fb-form-loading"),Promise.resolve(o(e.detail&&e.detail.values||{})).catch(function(){}).finally(function(){n=!1,t.classList.remove("fb-form-loading")}))})}function v(t,o,n){const e=t.querySelector(".fb-form-success, .fb-form-error");e&&e.remove();const i=document.createElement("div");i.className=n==="success"?"fb-form-success":"fb-form-error",i.textContent=o,t.appendChild(i)}function A(t,o){if(t._formLogicRuntime=o,t._formLogicObserver&&(t._formLogicObserver.disconnect(),t._formLogicObserver=null),!t.parentNode||typeof MutationObserver>"u"||t._formLogicObserved)return;t._formLogicObserved=!0;const n=new MutationObserver(function(e){for(const i of e)for(const r of i.removedNodes)if(r===t||r.nodeType===1&&r.contains&&r.contains(t)){t._formLogicRuntime&&t._formLogicRuntime.destroy(),n.disconnect();return}});n.observe(t.parentNode,{childList:!0,subtree:!1}),t._formLogicObserver=n}function re(t){const o=t&&t.logic;return!!(o&&(o.visibility||o.requirement))}function z(t,o){const n={};return t.forEach(function(e){if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const i=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),r=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":i?"chooser":e.type,d={...e.formConfig||{}};d.span==="full"&&o&&(d.span=o);const s={type:r,label:e.label||e.name,required:re(e)?!1:e.required||!1,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{helperText:e.helper},...e.tooltip&&{tooltip:e.tooltip},...d}};e.minLength!==void 0&&(s.minLength=e.minLength),e.maxLength!==void 0&&(s.maxLength=e.maxLength),e.min!==void 0&&(s.min=e.min),e.max!==void 0&&(s.max=e.max),i&&(e.variant&&(s.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(s.multiple=!0),e.density&&(s.density=e.density),e.columns&&(s.columns=e.columns),e.accent&&(s.accent=e.accent),e.accentStyle&&(s.accentStyle=e.accentStyle),e.glow&&(s.glow=!0),e.glowColour&&(s.glowColour=e.glowColour),e.shadow&&(s.shadow=e.shadow),e.shadowColour&&(s.shadowColour=e.shadowColour)),n[e.name]=e.type==="file"?Z(e,s):s}),n}function ie(t){const o={};return t.forEach(function(n){if(!(!n.name||n.type==="page-break"||n.type==="spacer")&&(n.type==="select"||n.type==="multiselect")&&n.required){const e=(n.options||[])[0];e&&(o[n.name]=typeof e=="object"?e.value:e)}}),o}function P(t,o){(o||[]).forEach(function(n){if(n.type!=="date"||!n.name)return;const e=t.querySelector('[name="'+n.name+'"]');e&&e.type!=="date"&&(e.type="date")})}const O="padding:.45rem .7rem;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);background:transparent;color:inherit;cursor:pointer;font:inherit;font-size:.85em;flex-shrink:0;line-height:1.2;";function D(t){const o=document.createElement("div");o.style.cssText="position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center;padding:20px;";const n=document.createElement("div");n.style.cssText="background:var(--dm-surface-overlay,var(--dm-surface));color:var(--dm-text);border:1px solid var(--dm-border);border-radius:10px;width:100%;max-width:560px;max-height:75vh;display:flex;flex-direction:column;gap:.6rem;padding:1rem;box-shadow:var(--dm-shadow-lg,0 12px 40px rgba(0,0,0,.35));";const e=document.createElement("div");e.style.cssText="display:flex;align-items:center;justify-content:space-between;gap:.5rem;";const i=document.createElement("strong");i.textContent=t;const r=document.createElement("button");r.type="button",r.textContent="\xD7",r.setAttribute("aria-label","Close"),r.style.cssText=O+"font-size:1rem;padding:.15rem .55rem;",e.appendChild(i),e.appendChild(r),n.appendChild(e);const d=document.createElement("div");d.style.cssText="display:flex;flex-direction:column;gap:.6rem;overflow:hidden;min-height:0;",n.appendChild(d),o.appendChild(n);const s=l=>{l.key==="Escape"&&a()};function a(){document.removeEventListener("keydown",s),o.remove()}return r.addEventListener("click",a),o.addEventListener("click",l=>{l.target===o&&a()}),document.addEventListener("keydown",s),document.body.appendChild(o),{body:d,close:a}}function ae(t){const{body:o,close:n}=D("Choose an icon"),e=document.createElement("input");e.type="text",e.placeholder="Search icons\u2026",e.className="form-input",e.style.cssText="padding:.45rem .6rem;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);background:var(--dm-input-bg,transparent);color:var(--dm-text);",o.appendChild(e);const i=document.createElement("div");i.style.cssText="display:grid;grid-template-columns:repeat(auto-fill,minmax(46px,1fr));gap:4px;overflow-y:auto;min-height:0;",o.appendChild(i);const r=window.I&&typeof I.list=="function"?I.list():[],d=r.map(s=>{const a=document.createElement("button");a.type="button",a.title=s,a.dataset.iconName=s,a.style.cssText="display:flex;align-items:center;justify-content:center;padding:.55rem 0;border:1px solid transparent;border-radius:6px;background:transparent;color:inherit;cursor:pointer;";const l=document.createElement("span");return l.setAttribute("data-icon",s),l.setAttribute("data-icon-size","18"),a.appendChild(l),a.addEventListener("mouseenter",()=>{a.style.borderColor="var(--dm-border)"}),a.addEventListener("mouseleave",()=>{a.style.borderColor="transparent"}),a.addEventListener("click",()=>{n(),t(s)}),i.appendChild(a),a});if(!r.length){const s=document.createElement("p");s.textContent="Icon engine unavailable - type an icon name into the field instead.",i.appendChild(s)}window.I?.scan?.(i),e.addEventListener("input",()=>{const s=e.value.trim().toLowerCase();d.forEach(a=>{a.style.display=!s||a.dataset.iconName.includes(s)?"":"none"})}),e.focus()}async function se(){const t=typeof S<"u"&&S.get?S.get("auth_token"):null;if(!t)return{error:"auth"};const o=i=>fetch("/api/media",{headers:{Authorization:"Bearer "+i}});let n;try{if(n=await o(t),n.status===401&&S.get("auth_refresh_token")){const i=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:S.get("auth_refresh_token")})});if(i.ok){const{token:r}=await i.json();S.set("auth_token",r),n=await o(r)}}}catch{return{error:"load"}}if(!n.ok)return{error:n.status===401||n.status===403?"auth":"load"};const e=await n.json().catch(()=>[]);return{files:(Array.isArray(e)?e:[]).filter(i=>/\.(png|jpe?g|gif|webp|svg|avif)$/i.test(i.name||i.url||""))}}function ce(t){const{body:o,close:n}=D("Choose an image"),e=document.createElement("p");e.textContent="Loading media library\u2026",e.style.cssText="margin:0;font-size:.85em;color:var(--dm-text-muted);",o.appendChild(e),se().then(({files:i,error:r})=>{if(r){e.textContent=r==="auth"?"Sign in with media access to browse the library - or paste an image URL into the field.":"Could not load the media library - paste an image URL into the field instead.";return}if(!i.length){e.textContent="No images in the media library yet.";return}e.remove();const d=document.createElement("div");d.style.cssText="display:grid;grid-template-columns:repeat(auto-fill,minmax(104px,1fr));gap:8px;overflow-y:auto;min-height:0;",i.forEach(s=>{const a=document.createElement("button");a.type="button",a.title=s.name||s.url,a.style.cssText="display:flex;flex-direction:column;gap:.25rem;padding:.35rem;border:1px solid var(--dm-border);border-radius:8px;background:transparent;color:inherit;cursor:pointer;min-width:0;";const l=document.createElement("img");l.src=s.url,l.alt=s.name||"",l.loading="lazy",l.style.cssText="width:100%;height:72px;object-fit:cover;border-radius:5px;";const f=document.createElement("span");f.textContent=s.name||s.url,f.style.cssText="font-size:.68em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;",a.appendChild(l),a.appendChild(f),a.addEventListener("click",()=>{n(),t(s.url)}),d.appendChild(a)}),o.appendChild(d)})}function M(t,o){t.value=o,t.dispatchEvent(new Event("input",{bubbles:!0})),t.dispatchEvent(new Event("change",{bubbles:!0}))}function V(t){const o=document.createElement("div");return o.style.cssText="display:flex;align-items:center;gap:.5rem;",t.parentNode.insertBefore(o,t),o.appendChild(t),t.style.flex="1",t.style.minWidth="0",o}function W(t){const o=document.createElement("button");return o.type="button",o.textContent=t,o.style.cssText=O,o}function le(t){if(!window.I||typeof I.list!="function")return;const o=V(t),n=document.createElement("span");n.style.cssText="display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;flex-shrink:0;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);";const e=()=>{n.textContent="";const r=t.value.trim();if(!r)return;const d=document.createElement("span");d.setAttribute("data-icon",r),d.setAttribute("data-icon-size","16"),n.appendChild(d),window.I?.scan?.(n)};t.addEventListener("input",e),e();const i=W("Browse");i.addEventListener("click",()=>ae(r=>{M(t,r),e()})),o.appendChild(n),o.appendChild(i)}function ue(t){const o=V(t),n=document.createElement("img");n.alt="",n.style.cssText="width:2rem;height:2rem;flex-shrink:0;object-fit:cover;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);display:none;";const e=()=>{const r=t.value.trim();n.style.display=r?"":"none",r&&(n.src=r)};t.addEventListener("input",e),e();const i=W("Browse");i.addEventListener("click",()=>ce(r=>{M(t,r),e()})),o.appendChild(n),o.appendChild(i)}export function enhancePickerFields(t,o){const n=typeof t=="string"?document.querySelector(t):t;n&&(o||[]).forEach(function(e){if(!e.name||e.type!=="icon"&&e.type!=="image")return;const i=n.querySelector('[name="'+e.name+'"]');!i||i.dataset.pickerEnhanced||(i.dataset.pickerEnhanced="1",e.type==="icon"?le(i):ue(i))})}function N(t,o){const n=document.createElement("div");n.className="fb-form-honeypot",n.setAttribute("aria-hidden","true");const e=document.createElement("input");e.name=t.querySelector('[name="website"]')?"_website":"website",n.dataset.hpName=e.name,e.type="text",e.tabIndex=-1,e.autocomplete="url",e.placeholder="https://",n.appendChild(e);const i=document.createElement("input");i.name="_t",i.type="hidden",i.value=o||Date.now(),n.appendChild(i),t.appendChild(n)}function U(t,o){const n=t.querySelector("form");if(!n)return;const e=Array.from(n.querySelectorAll(".form-group"));let i=0;o.forEach(function(r){if(r.type==="spacer"){const d=document.createElement("div");d.className="fb-spacer";const s=e[i];if(s)n.insertBefore(d,s);else{const a=n.querySelector('[type="submit"]');a?n.insertBefore(d,a):n.appendChild(d)}}else r.type!=="page-break"&&i++})}function x(t,o,n,e,i,r,d){const s=i||e,a=s.querySelector(".fb-form-honeypot"),l=a?.querySelector('input[type="text"]')?.value||"",f=a?.querySelector('[name="_t"]')?.value||"",u=Object.assign({},o);delete u._t,a?.dataset.hpName&&delete u[a.dataset.hpName];const c=new Map(r||[]);L(s,c);const h=te(u,d,c),{body:p,headers:w}=ne(Object.assign(h.values,{_hp:l,_t:f}),h.files);return fetch("/api/forms/submit/"+t,{method:"POST",headers:w,body:p}).then(g=>g.json().then(b=>({ok:g.ok,body:b}))).then(g=>{g.ok&&g.body.ok?(Q(e),e.textContent="",v(e,g.body.message||n.successMessage||"Thank you.","success")):v(e,g.body.error||"Something went wrong.","error")}).catch(()=>{v(e,"Unable to submit. Please check your connection.","error")})}function de(t,o,n,e,i){const r=document.createElement("form");r.noValidate=!0,o.forEach(function(a){const l=document.createElement("div");l.className="form-group",l.style.marginBottom="1.25rem",a.name&&(l.dataset.field=a.name);const f=document.createElement("label");if(f.className="form-label",f.textContent=a.label||a.name,a.required){const c=document.createElement("span");c.textContent=" *",c.style.color="var(--dm-danger)",f.appendChild(c)}let u;a.type==="textarea"?(u=document.createElement("textarea"),u.rows=a.formConfig?.rows||4,u.className="form-input"):a.type==="select"?(u=document.createElement("select"),u.className="form-input",(a.options||[]).forEach(function(c){const h=document.createElement("option");h.value=typeof c=="string"?c:c.value??"",h.textContent=typeof c=="string"?c:c.label||h.value,u.appendChild(h)})):(u=document.createElement("input"),u.type=a.type==="icon"||a.type==="image"?"text":a.type||"text",u.className="form-input",a.placeholder&&(u.placeholder=a.placeholder),a.type==="file"&&(a.file?.accept&&(u.accept=a.file.accept),a.multiple&&(u.multiple=!0))),u.name=a.name,u.required=a.required||!1,l.appendChild(f),l.appendChild(u),r.appendChild(l)}),n.honeypot&&N(r);const d=document.createElement("button");if(d.type="submit",d.className="btn btn-primary",d.textContent=n.submitText||"Submit",$(n,"stacked").length||Y(n)){const a=document.createElement("div");a.className="form-group form-buttons",a.appendChild(d),r.appendChild(a)}else r.appendChild(d);if(r.addEventListener("submit",function(a){a.preventDefault();const l={};if(o.forEach(function(u){const c=r.querySelector('[name="'+u.name+'"]');c&&(l[u.name]=c.value)}),window.FormLogicEngine&&i){const u=window.FormLogicEngine,c=[],h=[];if(o.forEach(function(p){if(u.evaluateFieldVisibility(p,l)==="hidden"){delete l[p.name];return}const g=u.evaluateFieldRequirement(p,l),b=l[p.name];g&&(!b||!String(b).trim())&&c.push(p.label||p.name);const C=u.validateField(p,b||"",l);C.length&&h.push(C[0].message)}),c.length||h.length){const p=[];c.length&&p.push("Required: "+c.join(", ")),h.length&&p.push(h.join("; ")),v(t,p.join(". "),"error");return}}const f=G(r,o);if(f.length){v(t,f.join(" "),"error");return}if(t.dataset.triggerBlocked==="true"){const u=t._formLogicRuntime,c=u&&u.submitBlock?u.submitBlock().reasons:[];v(t,c.join(" ")||"This form cannot be submitted yet.","error");return}t.classList.add("fb-form-loading"),d.disabled=!0,x(e,l,n,t,r,null,o).finally(function(){t.classList.remove("fb-form-loading"),d.disabled=!1})}),t.appendChild(r),_(r,n,"stacked"),F(r,n,o),enhancePickerFields(t,o),X(t,o),q(t,function(a){return x(e,a,n,t,r,null,o)}),window.FormLogicEngine&&i&&T(o)){const a=new window.FormLogicEngine.FormLogicRuntime(i,t);a.init(),t._formLogicRuntime=a}}function me(t){const o=t.getAttribute("data-form");o&&fetch("/api/forms/"+o+"/public").then(n=>{if(!n.ok)throw new Error("Form not found: "+o);return n.json()}).then(n=>{const e=n.fields||[],i=n.settings||{},r=document.createElement("div");r.className="fb-form-wrapper",t.appendChild(r);const d=e.some(s=>s.type==="page-break");if(typeof Domma<"u"&&Domma.forms){const s=Number(i.columns)===3?3:2,a=i.layout||"grid";if(d&&Domma.forms.wizard){const l=K(e,n.title),f=l.map(m=>m.fields),u=l.map(function(m){return{title:m.title,description:m.description,fields:z(m.fields,s)}});let c=0,h=null,p=null;const w=Date.now(),g=new Map;j(r,e);const b=function(m){const y=window.FormLogicEngine;if(!y||!y.validateFields)return;const k=y.validateFields(r,f[c]||[]);k.valid||(m.preventDefault(),m.stopImmediatePropagation(),markInvalidFields(r,k.missing),showValidationPopup(k))},C=function(){const m=f[c]||e;if(J(r,i),F(r,i,m),P(r,m),enhancePickerFields(r,m),ee(r,g),m.some(y=>y.type==="spacer")&&U(r,m),i.honeypot){const y=r.querySelector("form");y&&N(y,w)}window.FormLogicEngine&&T(e)&&(h&&h.destroy(),h=new window.FormLogicEngine.FormLogicRuntime(n,r,{baseValues:p&&p.getStepData?p.getStepData():{}}),h.init(),A(r,h)),r.querySelectorAll(".wizard-next, .wizard-submit").forEach(y=>y.addEventListener("click",b,!0))};p=Domma.forms.wizard(r,{schema:{steps:u},layout:a,columns:s,finishText:i.submitText||"Finish",onStepChange:function(m){L(r,g),c=m,setTimeout(C,0)},onSubmit:function(m){return L(r,g),x(o,m,i,r,null,g,e)}}),q(r,function(m){return L(r,g),x(o,m,i,r,null,g,e)}),r.addEventListener("dm-form-trigger",function(m){if(!m.detail||m.detail.type!=="goto-step"||!p||!p.goToStep)return;const y=Number(m.detail.action.step);!isFinite(y)||y<0||y>=u.length||y!==c&&p.goToStep(y)}),Promise.resolve(p).then(C)}else if(Domma.forms.render){const l=z(e,s),f=ie(e);j(r,e),q(r,function(c){return x(o,c,i,r,null,null,e)});const u=Domma.forms.render(r,l,f,{submitText:i.submitText||"Submit",layout:a,columns:s,onSubmit:function(c){return x(o,c,i,r,null,null,e)}});Promise.resolve(u).then(function(){if(_(r,i,a),F(r,i,e),P(r,e),enhancePickerFields(r,e),window.FormLogicEngine&&T(e)){const c=new window.FormLogicEngine.FormLogicRuntime(n,r);c.init(),A(r,c)}if(e.some(c=>c.type==="spacer")&&U(r,e),i.honeypot){const c=r.querySelector("form");c&&N(c)}})}}else de(r,e.filter(s=>s.type!=="page-break"&&s.type!=="spacer"),i,o,n)}).catch(n=>{const e=document.createElement("p");e.textContent="Form unavailable.",e.style.cssText="color:var(--dm-danger);font-style:italic;",t.appendChild(e),console.warn("[forms]",n.message)})}
1
+ import{installSmoothCarets as K}from"/public/js/smooth-caret.js";import{buildWizardSteps as J}from"/public/js/form-steps.mjs";import{applyFormClasses as F,applySubmitButtons as _,applyWizardButtons as Y,resolveSecondaryButton as $,submitWrapperClasses as G}from"/public/js/form-submit.mjs";import{captureFileFields as L,checkFileInputs as Q,destroyDropzones as X,enhanceFileInputs as Z,fileBlueprint as ee,restoreFileFields as te,setFileLimits as j,splitFiles as ne,submitBody as oe,totalTooLarge as re,wireFileFields as B}from"/public/js/form-files.mjs";K(document);const R=document.querySelectorAll("[data-form]");R.length&&R.forEach(fe);function T(n){return(n||[]).some(o=>o.logic||Array.isArray(o.triggers)&&o.triggers.length)}function ie(n,o){const t=n.querySelector('[name="'+o+'"], [name="'+o+'[]"]')||n.querySelector('[data-chooser-field="'+o+'"]');if(!t)return null;const e=t.closest("form")||n;let i=t;for(;i.parentNode&&i.parentNode!==e;)i=i.parentNode;return i}export function markInvalidFields(n,o){n.querySelectorAll(".fb-field-invalid").forEach(e=>e.classList.remove("fb-field-invalid"));let t=null;(o||[]).forEach(function(e){const i=ie(n,e.name);i&&(i.classList.add("fb-field-invalid"),t||(t=i))}),t&&t.scrollIntoView&&t.scrollIntoView({behavior:"smooth",block:"center"})}export function showValidationPopup(n){const o=(n.missing||[]).map(e=>"<strong>"+A(e.label)+"</strong> is required.").concat((n.errors||[]).map(A)),t=o.join("<br>");if(typeof E<"u"&&E.alert){E.alert(t,{title:"Please complete this step"});return}window.alert(o.join(`
2
+ `).replace(/<[^>]+>/g,""))}function A(n){return String(n).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function q(n,o){let t=!1;n.addEventListener("dm-form-end",function(e){const i=e.detail&&e.detail.validation;if(i&&!i.valid){markInvalidFields(n,i.missing),showValidationPopup(i);return}t||(t=!0,n.classList.add("fb-form-loading"),Promise.resolve(o(e.detail&&e.detail.values||{})).catch(function(){}).finally(function(){t=!1,n.classList.remove("fb-form-loading")}))})}function x(n,o,t){const e=n.querySelector(".fb-form-success, .fb-form-error");e&&e.remove();const i=document.createElement("div");i.className=t==="success"?"fb-form-success":"fb-form-error",i.textContent=o,n.appendChild(i)}function P(n,o){if(n._formLogicRuntime=o,n._formLogicObserver&&(n._formLogicObserver.disconnect(),n._formLogicObserver=null),!n.parentNode||typeof MutationObserver>"u"||n._formLogicObserved)return;n._formLogicObserved=!0;const t=new MutationObserver(function(e){for(const i of e)for(const r of i.removedNodes)if(r===n||r.nodeType===1&&r.contains&&r.contains(n)){n._formLogicRuntime&&n._formLogicRuntime.destroy(),t.disconnect();return}});t.observe(n.parentNode,{childList:!0,subtree:!1}),n._formLogicObserver=t}function ae(n){const o=n&&n.logic;return!!(o&&(o.visibility||o.requirement))}function z(n,o){const t={};return n.forEach(function(e){if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const i=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),r=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":i?"chooser":e.type,d={...e.formConfig||{}};d.span==="full"&&o&&(d.span=o);const s={type:r,label:e.label||e.name,required:ae(e)?!1:e.required||!1,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{helperText:e.helper},...e.tooltip&&{tooltip:e.tooltip},...d}};e.minLength!==void 0&&(s.minLength=e.minLength),e.maxLength!==void 0&&(s.maxLength=e.maxLength),e.min!==void 0&&(s.min=e.min),e.max!==void 0&&(s.max=e.max),i&&(e.variant&&(s.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(s.multiple=!0),e.density&&(s.density=e.density),e.columns&&(s.columns=e.columns),e.accent&&(s.accent=e.accent),e.accentStyle&&(s.accentStyle=e.accentStyle),e.glow&&(s.glow=!0),e.glowColour&&(s.glowColour=e.glowColour),e.shadow&&(s.shadow=e.shadow),e.shadowColour&&(s.shadowColour=e.shadowColour)),t[e.name]=e.type==="file"?ee(e,s):s}),t}function se(n){const o={};return n.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&&(o[t.name]=typeof e=="object"?e.value:e)}}),o}function O(n,o){(o||[]).forEach(function(t){if(t.type!=="date"||!t.name)return;const e=n.querySelector('[name="'+t.name+'"]');e&&e.type!=="date"&&(e.type="date")})}const D="padding:.45rem .7rem;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);background:transparent;color:inherit;cursor:pointer;font:inherit;font-size:.85em;flex-shrink:0;line-height:1.2;";function M(n){const o=document.createElement("div");o.style.cssText="position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center;padding:20px;";const t=document.createElement("div");t.style.cssText="background:var(--dm-surface-overlay,var(--dm-surface));color:var(--dm-text);border:1px solid var(--dm-border);border-radius:10px;width:100%;max-width:560px;max-height:75vh;display:flex;flex-direction:column;gap:.6rem;padding:1rem;box-shadow:var(--dm-shadow-lg,0 12px 40px rgba(0,0,0,.35));";const e=document.createElement("div");e.style.cssText="display:flex;align-items:center;justify-content:space-between;gap:.5rem;";const i=document.createElement("strong");i.textContent=n;const r=document.createElement("button");r.type="button",r.textContent="\xD7",r.setAttribute("aria-label","Close"),r.style.cssText=D+"font-size:1rem;padding:.15rem .55rem;",e.appendChild(i),e.appendChild(r),t.appendChild(e);const d=document.createElement("div");d.style.cssText="display:flex;flex-direction:column;gap:.6rem;overflow:hidden;min-height:0;",t.appendChild(d),o.appendChild(t);const s=l=>{l.key==="Escape"&&a()};function a(){document.removeEventListener("keydown",s),o.remove()}return r.addEventListener("click",a),o.addEventListener("click",l=>{l.target===o&&a()}),document.addEventListener("keydown",s),document.body.appendChild(o),{body:d,close:a}}function ce(n){const{body:o,close:t}=M("Choose an icon"),e=document.createElement("input");e.type="text",e.placeholder="Search icons\u2026",e.className="form-input",e.style.cssText="padding:.45rem .6rem;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);background:var(--dm-input-bg,transparent);color:var(--dm-text);",o.appendChild(e);const i=document.createElement("div");i.style.cssText="display:grid;grid-template-columns:repeat(auto-fill,minmax(46px,1fr));gap:4px;overflow-y:auto;min-height:0;",o.appendChild(i);const r=window.I&&typeof I.list=="function"?I.list():[],d=r.map(s=>{const a=document.createElement("button");a.type="button",a.title=s,a.dataset.iconName=s,a.style.cssText="display:flex;align-items:center;justify-content:center;padding:.55rem 0;border:1px solid transparent;border-radius:6px;background:transparent;color:inherit;cursor:pointer;";const l=document.createElement("span");return l.setAttribute("data-icon",s),l.setAttribute("data-icon-size","18"),a.appendChild(l),a.addEventListener("mouseenter",()=>{a.style.borderColor="var(--dm-border)"}),a.addEventListener("mouseleave",()=>{a.style.borderColor="transparent"}),a.addEventListener("click",()=>{t(),n(s)}),i.appendChild(a),a});if(!r.length){const s=document.createElement("p");s.textContent="Icon engine unavailable - type an icon name into the field instead.",i.appendChild(s)}window.I?.scan?.(i),e.addEventListener("input",()=>{const s=e.value.trim().toLowerCase();d.forEach(a=>{a.style.display=!s||a.dataset.iconName.includes(s)?"":"none"})}),e.focus()}async function le(){const n=typeof S<"u"&&S.get?S.get("auth_token"):null;if(!n)return{error:"auth"};const o=i=>fetch("/api/media",{headers:{Authorization:"Bearer "+i}});let t;try{if(t=await o(n),t.status===401&&S.get("auth_refresh_token")){const i=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:S.get("auth_refresh_token")})});if(i.ok){const{token:r}=await i.json();S.set("auth_token",r),t=await o(r)}}}catch{return{error:"load"}}if(!t.ok)return{error:t.status===401||t.status===403?"auth":"load"};const e=await t.json().catch(()=>[]);return{files:(Array.isArray(e)?e:[]).filter(i=>/\.(png|jpe?g|gif|webp|svg|avif)$/i.test(i.name||i.url||""))}}function ue(n){const{body:o,close:t}=M("Choose an image"),e=document.createElement("p");e.textContent="Loading media library\u2026",e.style.cssText="margin:0;font-size:.85em;color:var(--dm-text-muted);",o.appendChild(e),le().then(({files:i,error:r})=>{if(r){e.textContent=r==="auth"?"Sign in with media access to browse the library - or paste an image URL into the field.":"Could not load the media library - paste an image URL into the field instead.";return}if(!i.length){e.textContent="No images in the media library yet.";return}e.remove();const d=document.createElement("div");d.style.cssText="display:grid;grid-template-columns:repeat(auto-fill,minmax(104px,1fr));gap:8px;overflow-y:auto;min-height:0;",i.forEach(s=>{const a=document.createElement("button");a.type="button",a.title=s.name||s.url,a.style.cssText="display:flex;flex-direction:column;gap:.25rem;padding:.35rem;border:1px solid var(--dm-border);border-radius:8px;background:transparent;color:inherit;cursor:pointer;min-width:0;";const l=document.createElement("img");l.src=s.url,l.alt=s.name||"",l.loading="lazy",l.style.cssText="width:100%;height:72px;object-fit:cover;border-radius:5px;";const h=document.createElement("span");h.textContent=s.name||s.url,h.style.cssText="font-size:.68em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;",a.appendChild(l),a.appendChild(h),a.addEventListener("click",()=>{t(),n(s.url)}),d.appendChild(a)}),o.appendChild(d)})}function V(n,o){n.value=o,n.dispatchEvent(new Event("input",{bubbles:!0})),n.dispatchEvent(new Event("change",{bubbles:!0}))}function W(n){const o=document.createElement("div");return o.style.cssText="display:flex;align-items:center;gap:.5rem;",n.parentNode.insertBefore(o,n),o.appendChild(n),n.style.flex="1",n.style.minWidth="0",o}function U(n){const o=document.createElement("button");return o.type="button",o.textContent=n,o.style.cssText=D,o}function de(n){if(!window.I||typeof I.list!="function")return;const o=W(n),t=document.createElement("span");t.style.cssText="display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;flex-shrink:0;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);";const e=()=>{t.textContent="";const r=n.value.trim();if(!r)return;const d=document.createElement("span");d.setAttribute("data-icon",r),d.setAttribute("data-icon-size","16"),t.appendChild(d),window.I?.scan?.(t)};n.addEventListener("input",e),e();const i=U("Browse");i.addEventListener("click",()=>ce(r=>{V(n,r),e()})),o.appendChild(t),o.appendChild(i)}function me(n){const o=W(n),t=document.createElement("img");t.alt="",t.style.cssText="width:2rem;height:2rem;flex-shrink:0;object-fit:cover;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);display:none;";const e=()=>{const r=n.value.trim();t.style.display=r?"":"none",r&&(t.src=r)};n.addEventListener("input",e),e();const i=U("Browse");i.addEventListener("click",()=>ue(r=>{V(n,r),e()})),o.appendChild(t),o.appendChild(i)}export function enhancePickerFields(n,o){const t=typeof n=="string"?document.querySelector(n):n;t&&(o||[]).forEach(function(e){if(!e.name||e.type!=="icon"&&e.type!=="image")return;const i=t.querySelector('[name="'+e.name+'"]');!i||i.dataset.pickerEnhanced||(i.dataset.pickerEnhanced="1",e.type==="icon"?de(i):me(i))})}function N(n,o){const t=document.createElement("div");t.className="fb-form-honeypot",t.setAttribute("aria-hidden","true");const e=document.createElement("input");e.name=n.querySelector('[name="website"]')?"_website":"website",t.dataset.hpName=e.name,e.type="text",e.tabIndex=-1,e.autocomplete="url",e.placeholder="https://",t.appendChild(e);const i=document.createElement("input");i.name="_t",i.type="hidden",i.value=o||Date.now(),t.appendChild(i),n.appendChild(t)}function H(n,o){const t=n.querySelector("form");if(!t)return;const e=Array.from(t.querySelectorAll(".form-group"));let i=0;o.forEach(function(r){if(r.type==="spacer"){const d=document.createElement("div");d.className="fb-spacer";const s=e[i];if(s)t.insertBefore(d,s);else{const a=t.querySelector('[type="submit"]');a?t.insertBefore(d,a):t.appendChild(d)}}else r.type!=="page-break"&&i++})}function C(n,o,t,e,i,r,d){const s=i||e,a=s.querySelector(".fb-form-honeypot"),l=a?.querySelector('input[type="text"]')?.value||"",h=a?.querySelector('[name="_t"]')?.value||"",u=Object.assign({},o);delete u._t,a?.dataset.hpName&&delete u[a.dataset.hpName];const c=new Map(r||[]);L(s,c);const f=ne(u,d,c),p=re(e,f.files);if(p)return x(e,p,"error"),Promise.resolve();const{body:w,headers:b}=oe(Object.assign(f.values,{_hp:l,_t:h}),f.files);return fetch("/api/forms/submit/"+n,{method:"POST",headers:b,body:w}).then(y=>y.json().then(v=>({ok:y.ok,body:v}))).then(y=>{y.ok&&y.body.ok?(X(e),e.textContent="",x(e,y.body.message||t.successMessage||"Thank you.","success")):x(e,y.body.error||"Something went wrong.","error")}).catch(()=>{x(e,"Unable to submit. Please check your connection.","error")})}function pe(n,o,t,e,i){const r=document.createElement("form");r.noValidate=!0,o.forEach(function(a){const l=document.createElement("div");l.className="form-group",l.style.marginBottom="1.25rem",a.name&&(l.dataset.field=a.name);const h=document.createElement("label");if(h.className="form-label",h.textContent=a.label||a.name,a.required){const c=document.createElement("span");c.textContent=" *",c.style.color="var(--dm-danger)",h.appendChild(c)}let u;a.type==="textarea"?(u=document.createElement("textarea"),u.rows=a.formConfig?.rows||4,u.className="form-input"):a.type==="select"?(u=document.createElement("select"),u.className="form-input",(a.options||[]).forEach(function(c){const f=document.createElement("option");f.value=typeof c=="string"?c:c.value??"",f.textContent=typeof c=="string"?c:c.label||f.value,u.appendChild(f)})):(u=document.createElement("input"),u.type=a.type==="icon"||a.type==="image"?"text":a.type||"text",u.className="form-input",a.placeholder&&(u.placeholder=a.placeholder),a.type==="file"&&(a.file?.accept&&(u.accept=a.file.accept),a.multiple&&(u.multiple=!0))),u.name=a.name,u.required=a.required||!1,l.appendChild(h),l.appendChild(u),r.appendChild(l)}),t.honeypot&&N(r);const d=document.createElement("button");if(d.type="submit",d.className="btn btn-primary",d.textContent=t.submitText||"Submit",G(t,"stacked").length||$(t)){const a=document.createElement("div");a.className="form-group form-buttons",a.appendChild(d),r.appendChild(a)}else r.appendChild(d);if(r.addEventListener("submit",function(a){a.preventDefault();const l={};if(o.forEach(function(u){const c=r.querySelector('[name="'+u.name+'"]');c&&(l[u.name]=c.value)}),window.FormLogicEngine&&i){const u=window.FormLogicEngine,c=[],f=[];if(o.forEach(function(p){if(u.evaluateFieldVisibility(p,l)==="hidden"){delete l[p.name];return}const b=u.evaluateFieldRequirement(p,l),y=l[p.name];b&&(!y||!String(y).trim())&&c.push(p.label||p.name);const v=u.validateField(p,y||"",l);v.length&&f.push(v[0].message)}),c.length||f.length){const p=[];c.length&&p.push("Required: "+c.join(", ")),f.length&&p.push(f.join("; ")),x(n,p.join(". "),"error");return}}const h=Q(r,o);if(h.length){x(n,h.join(" "),"error");return}if(n.dataset.triggerBlocked==="true"){const u=n._formLogicRuntime,c=u&&u.submitBlock?u.submitBlock().reasons:[];x(n,c.join(" ")||"This form cannot be submitted yet.","error");return}n.classList.add("fb-form-loading"),d.disabled=!0,C(e,l,t,n,r,null,o).finally(function(){n.classList.remove("fb-form-loading"),d.disabled=!1})}),n.appendChild(r),_(r,t,"stacked"),F(r,t,o),enhancePickerFields(n,o),Z(n,o),q(n,function(a){return C(e,a,t,n,r,null,o)}),window.FormLogicEngine&&i&&T(o)){const a=new window.FormLogicEngine.FormLogicRuntime(i,n);a.init(),n._formLogicRuntime=a}}function fe(n){const o=n.getAttribute("data-form");o&&fetch("/api/forms/"+o+"/public").then(t=>{if(!t.ok)throw new Error("Form not found: "+o);return t.json()}).then(t=>{const e=t.fields||[],i=t.settings||{},r=document.createElement("div");r.className="fb-form-wrapper",n.appendChild(r),j(r,t.fileLimits);const d=e.some(s=>s.type==="page-break");if(typeof Domma<"u"&&Domma.forms){const s=Number(i.columns)===3?3:2,a=i.layout||"grid";if(d&&Domma.forms.wizard){const l=J(e,t.title),h=l.map(m=>m.fields),u=l.map(function(m){return{title:m.title,description:m.description,fields:z(m.fields,s)}});let c=0,f=null,p=null;const w=Date.now(),b=new Map;j(r,t.fileLimits,b),B(r,e);const y=function(m){const g=window.FormLogicEngine;if(!g||!g.validateFields)return;const k=g.validateFields(r,h[c]||[]);k.valid||(m.preventDefault(),m.stopImmediatePropagation(),markInvalidFields(r,k.missing),showValidationPopup(k))},v=function(){const m=h[c]||e;if(Y(r,i),F(r,i,m),O(r,m),enhancePickerFields(r,m),te(r,b),m.some(g=>g.type==="spacer")&&H(r,m),i.honeypot){const g=r.querySelector("form");g&&N(g,w)}window.FormLogicEngine&&T(e)&&(f&&f.destroy(),f=new window.FormLogicEngine.FormLogicRuntime(t,r,{baseValues:p&&p.getStepData?p.getStepData():{}}),f.init(),P(r,f)),r.querySelectorAll(".wizard-next, .wizard-submit").forEach(g=>g.addEventListener("click",y,!0))};p=Domma.forms.wizard(r,{schema:{steps:u},layout:a,columns:s,finishText:i.submitText||"Finish",onStepChange:function(m){L(r,b),c=m,setTimeout(v,0)},onSubmit:function(m){return L(r,b),C(o,m,i,r,null,b,e)}}),q(r,function(m){return L(r,b),C(o,m,i,r,null,b,e)}),r.addEventListener("dm-form-trigger",function(m){if(!m.detail||m.detail.type!=="goto-step"||!p||!p.goToStep)return;const g=Number(m.detail.action.step);!isFinite(g)||g<0||g>=u.length||g!==c&&p.goToStep(g)}),Promise.resolve(p).then(v)}else if(Domma.forms.render){const l=z(e,s),h=se(e);B(r,e),q(r,function(c){return C(o,c,i,r,null,null,e)});const u=Domma.forms.render(r,l,h,{submitText:i.submitText||"Submit",layout:a,columns:s,onSubmit:function(c){return C(o,c,i,r,null,null,e)}});Promise.resolve(u).then(function(){if(_(r,i,a),F(r,i,e),O(r,e),enhancePickerFields(r,e),window.FormLogicEngine&&T(e)){const c=new window.FormLogicEngine.FormLogicRuntime(t,r);c.init(),P(r,c)}if(e.some(c=>c.type==="spacer")&&H(r,e),i.honeypot){const c=r.querySelector("form");c&&N(c)}})}}else pe(r,e.filter(s=>s.type!=="page-break"&&s.type!=="spacer"),i,o,t)}).catch(t=>{const e=document.createElement("p");e.textContent="Form unavailable.",e.style.cssText="color:var(--dm-danger);font-style:italic;",n.appendChild(e),console.warn("[forms]",t.message)})}
package/public/js/site.js CHANGED
@@ -1 +1 @@
1
- import{applyMobileMenu as se,initCarouselPopup as ie}from"/public/js/mobile-ui.mjs?v=20261016-mobile-options";import{installSmoothCarets as re}from"/public/js/smooth-caret.js";import{decodeBase64Json as W}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as U,applyColour as G,applyPill as ce,applyFloat as le,applyHighlight as V,applyItemHighlight as de,itemHighlightAttrs as me,isCurrentItem as K,menuAlign as ue,applyClasses as P,cssClasses as Y,applyEntrance as J}from"/public/js/menu-decor.mjs?v=20261014-menu-entrance";import{initFloatingMenus as pe}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as Q,markInvalidFields as Z,showValidationPopup as ee}from"/public/js/forms.js?v=20261025-form-dropzone";import{buildWizardSteps as fe}from"/public/js/form-steps.mjs";import{applyFormClasses as te,applySubmitButtons as he,applyWizardButtons as ge}from"/public/js/form-submit.mjs";import{captureFileFields as oe,destroyDropzones as ye,fileBlueprint as ve,restoreFileFields as be,splitFiles as we,submitBody as Ce,wireFileFields as Se}from"/public/js/form-files.mjs";document.addEventListener("click",m=>{const c=m.target?.closest?.(".dm-banner__dismiss");c&&(m.preventDefault(),c.closest(".dm-banner")?.remove())}),$(()=>{re(document);const m=window.__CMS_NAV__||{},c=window.__CMS_SITE__||{};if(c.autoTheme?.enabled){let o=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>=o(n.dayStart)&&a<o(n.nightStart)?n.dayTheme:n.nightTheme},t=function(e){const a=n.apply&&n.apply[e]||{theme:e,custom:""};Domma.theme.set(a.theme);for(const d of[...document.body.classList])d.startsWith("dm-custom-")&&document.body.classList.remove(d);a.custom&&document.body.classList.add(a.custom)};const n=c.autoTheme;t(s()),setInterval(()=>t(s()),6e4)}const x=$("#site-navbar");if(x.length&&m.brand){let s=function(u,y=0){return Array.isArray(u)?u.filter(h=>!(h&&(h.type==="spacer"||h.type==="theme-switcher"))).filter(h=>!(h&&h.type==="separator"&&y===0)).map(h=>h&&h.type==="separator"?{divider:!0}:Array.isArray(h?.items)?{...h,items:s(h.items,y+1)}:h):u};const n={...m.brand},o=n.size&&n.size!=="md"?` navbar-brand-${n.size}`:"";if(n.logo||n.icon||n.tagline){let u="";n.logo?u+=`<img src="${n.logo}" class="navbar-brand-logo" alt="${n.text||""}">`:n.icon&&(u+=`<span data-icon="${n.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),n.text&&(u+=`<span class="navbar-brand-text${o}">${n.text}</span>`),n.tagline&&(u+=`<small class="navbar-brand-tagline">${n.tagline}</small>`),n.html=u}else o&&n.text&&(n.html=`<span class="navbar-brand-text${o}">${n.text}</span>`);const t=s(m.items||[]),e=m.position==="floating",a=m.orientation==="vertical";x.get(0).querySelector(":scope > .cms-nojs-links")?.remove(),Domma.elements.navbar("#site-navbar",{brand:n,items:t,variant:m.variant||"dark",position:e?"static":m.position||"sticky",collapsible:!0,appearOnHover:!!m.appearOnHover}),Domma.icons.scan("#site-navbar");const d=x.get(0);se(d,m.mobile);const p=()=>{if(!a)return;const y=d.querySelector('a[aria-current="page"]')?.closest(".navbar-dropdown");y&&!y.classList.contains("open")&&y.querySelector(":scope > .navbar-dropdown-toggle")?.click()};d.classList.add(`dm-nav-align-${ue(m)||"centre"}`),a&&x.get(0).classList.add("dm-nav-vertical",m.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left");const i=m.dock||{};a&&!e&&xe(d,m.side==="right"),!a&&i.hideOnScroll&&ke(d),V(d,m),J(d,m),P(d,m.class),e&&(le(x.get(0),m),pe(x.get(0).parentNode||document));const r=d.querySelector(".navbar-toggle"),g=d.querySelector(".navbar-collapse");if(r&&g){let u=null;const y=()=>{g.classList.contains("show")&&r.click()},h=()=>{u&&(u.remove(),u=null),document.body.classList.remove("dm-drawer-open")},f=()=>{u||(u=document.createElement("div"),u.className="dm-navbar-backdrop",u.addEventListener("click",y),document.body.appendChild(u),document.body.classList.add("dm-drawer-open"))};r.addEventListener("click",()=>{requestAnimationFrame(()=>{g.classList.contains("show")?(p(),f()):h()})}),g.addEventListener("click",w=>{w.target.closest("a")&&requestAnimationFrame(h)}),document.addEventListener("keydown",w=>{w.key==="Escape"&&g.classList.contains("show")&&y()})}const b=d.querySelector(".navbar-nav");if(b){const u=Array.from(b.children);let y=0;for(const h of m.items||[])if(!(h&&h.type==="separator")){if(h&&h.type==="spacer"){const f=document.createElement("li"),w=["xs","sm","md","lg","xl","flex"].includes(h.size)?h.size:"md";f.className=`dm-menu-spacer dm-menu-spacer--${w}`,f.setAttribute("aria-hidden","true"),b.insertBefore(f,u[y]||null);continue}if(h&&h.type==="theme-switcher"){const f=document.createElement("li");f.className="dm-menu-theme-switcher";const w=document.createElement("span");w.dataset.themeSwitcher="menu",w.dataset.position="inline",f.appendChild(w),b.insertBefore(f,u[y]||null);continue}y++}window.CMSThemeSwitcher?.scan(b)}d.querySelectorAll("[data-path]").forEach(u=>{const y=u.dataset.path.split(".").map(Number),h=y.length>1,f=y.reduce((A,I)=>A?.items?.[I],{items:t});if(!f)return;f.colour&&G(u,f.colour),f.pill&&!h&&ce(u,f.pill),de(u,f),P(u.closest("li"),f.class);const w=U(f.badge);w&&u.appendChild(w),K(f.url,location.pathname)&&(u.classList.add("active"),u.setAttribute("aria-current","page"))}),p()}const v=$("#site-footer");if(v.length){let e=function(l){return String(l??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},a=function(l){return e(l).replace(/'/g,"&#39;")},d=function(l){if(l&&l.type==="separator")return'<li class="footer-sep"><hr></li>';if(l&&l.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(l.size)?l.size:"md"}" aria-hidden="true"></li>`;if(l&&l.type==="theme-switcher")return'<li class="dm-menu-theme-switcher"><span data-theme-switcher="menu" data-position="inline"></span></li>';const D=a(l.url||"#"),L=e(l.text||""),q=Array.isArray(l.items)&&l.items.length?`<ul>${l.items.map(d).join("")}</ul>`:"",j=me(l),ne=[j.hover&&` data-menu-item-hover="${a(j.hover)}"`,j.active&&` data-menu-item-active="${a(j.active)}"`].filter(Boolean).join(""),ae=j.accent?` style="--dm-menu-highlight:${a(j.accent)}"`:"",X=Y(l.class).join(" ");return`<li${X?` class="${a(X)}"`:""}><a href="${D}"${ne}${ae}>${L}</a>${q}</li>`},i=function(){if(!c.footer)return"";const l=Object.keys(o).filter(L=>n[L]);if(!l.length)return"";const D=Y(c.footer.socialClass).join(" ");return`<div class="footer-social">${l.map(L=>{const{label:q,svg:j}=o[L];return`<a href="${n[L]}" target="_blank" rel="noopener noreferrer" aria-label="${q}" class="footer-social-link${D?` ${a(D)}`:""}">${j}</a>`}).join("")}</div>`},u=function(l){if(!l||l.type==="separator"||l.type==="spacer")return"";if(l.type==="theme-switcher")return`<li class="cms-footer-col">${d(l).replace(/^<li[^>]*>|<\/li>$/g,"")}</li>`;const D=Array.isArray(l.items)&&l.items.length?`<ul>${l.items.map(d).join("")}</ul>`:"",L=b(l)?`<span class="cms-footer-col-title">${e(l.text||"")}</span>`:`<a class="cms-footer-col-title" href="${a(l.url||"#")}">${e(l.text||"")}</a>`,q=["cms-footer-col",...Y(l.class)].join(" ");return`<li class="${a(q)}">${L}${D}</li>`},f=function(l,D,L=!1){return(l||[]).forEach(q=>{q&&(q.type==="separator"||q.type==="spacer"||q.type==="theme-switcher")||(L&&b(q)||D.push(q),Array.isArray(q.items)&&q.items.length&&f(q.items,D))}),D};const n=c.social||{},o={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:""},p=(()=>{const l=c.cookieConsent;if(!l||l.showSettingsLink===!1)return"";const D=L=>(L||[]).some(q=>q&&(q.url==="#cookie-settings"||D(q.items)));return D(t.primary)||D(t.legal)?"":`<li class="cms-cookie-settings"><a href="#cookie-settings">${e(l.settingsLinkText||"Cookie settings")}</a></li>`})(),r=["columns","row"].includes(c.footer?.layout)?c.footer.layout:"auto",g=r==="columns"||r==="auto"&&t.primary.some(l=>Array.isArray(l?.items)&&l.items.length),b=l=>g&&!l.url&&Array.isArray(l.items)&&l.items.length>0;if(g){const l=m.brand||{},D=l.text||c.title||"",L=l.logo?`<img src="${a(l.logo)}" class="cms-footer-brand-logo" alt="">`:l.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">${D||L?`<a class="cms-footer-brand-name" href="${a(l.url||"/")}">${L}<span>${e(D)}</span></a>`:""}${q?`<p class="cms-footer-blurb">${e(q)}</p>`:""}${i()}</div>`,t.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary footer-nav--columns" aria-label="Footer"><ul>${t.primary.map(u).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||p)&&(s+=`<nav class="footer-nav footer-nav--legal" aria-label="Legal"><ul>${t.legal.map(d).join("")}${p}</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(d).join("")}</ul></nav>`),(t.legal.length||p)&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${t.legal.map(d).join("")}${p}</ul></nav>`),s+=i();s+="</div>",v.get(0).querySelector(":scope > .cms-nojs-links")?.remove();const y=v.get(0).querySelector(":scope > .cms-footer-blocks");y?.remove(),v.html(s),y&&v.get(0).prepend(y),window.CMSThemeSwitcher?.scan(v.get(0));const h=v.get(0).querySelector(".cms-footer-brand-icon");h&&m.brand?.icon&&h.setAttribute("data-icon",m.brand.icon),P(v.get(0),c.footer?.class);const w=[...f(t.primary,[],!0),...f(t.legal,[])];v.get(0).querySelectorAll(".footer-nav a").forEach((l,D)=>{const L=w[D];if(!L)return;L.colour&&G(l,L.colour);const q=U(L.badge);q&&l.appendChild(q),K(L.url,location.pathname)&&(l.classList.add("is-active"),l.setAttribute("aria-current","page"))}),V(v.get(0).querySelector(".footer-nav--primary"),{highlight:t.primaryHighlight}),V(v.get(0).querySelector(".footer-nav--legal"),{highlight:t.legalHighlight}),J(v.get(0).querySelector(".footer-nav--primary"),{entrance:t.primaryEntrance}),J(v.get(0).querySelector(".footer-nav--legal"),{entrance:t.legalEntrance}),P(v.get(0).querySelector(".footer-nav--primary"),t.primaryClass),P(v.get(0).querySelector(".footer-nav--legal"),t.legalClass);const I=c.footer?.showMotionToggle!==!1,N=S.get("reduced_motion"),O=c.footer?.motionDefault,B=N!=null?!!N:O==="on"?!0:O==="off"?!1:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),M=c.footer?.motionControl==="checkbox",z=v.get(0).querySelector(".cms-footer-bottom")||v.get(0).querySelector(".footer-inner");if(I&&z){const l=document.createElement("input");l.type="checkbox",l.className=M?"footer-motion-checkbox":"form-switch-input",l.id="dm-motion-switch",l.checked=B,l.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const D=document.createElement("span");D.className=M?"footer-motion-label":"form-switch-label",D.textContent="Reduce motion";const L=document.createElement("label");L.className=M?"footer-motion-check":"form-switch footer-motion-switch",L.title="Reduce motion",L.appendChild(l),L.appendChild(D),z.appendChild(L)}}const k=document.getElementById("site-sidebar");k&&Le(k),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const C=$(".page-body");if(C.length){C.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),C.find(".tabs").each(function(){Domma.elements.tabs(this)}),C.find(".carousel").each(function(){const n={primary:"--dm-primary",secondary:"--dm-secondary",accent:"--dm-primary",text:"--dm-text",surface:"--dm-surface",background:"--dm-background",success:"--dm-success",warning:"--dm-warning",danger:"--dm-danger",info:"--dm-info"},o=p=>{const i=String(p||"").trim();return i?n[i.toLowerCase()]?`var(${n[i.toLowerCase()]})`:CSS.supports("color",i)?i:"":""},s=o(this.dataset.background),t=o(this.dataset.arrowBackground),e=o(this.dataset.arrowColor);s&&(this.style.setProperty("--dm-carousel-background",s),this.classList.add("dm-carousel-custom-background")),t&&this.style.setProperty("--dm-carousel-arrow-background",t),e&&this.style.setProperty("--dm-carousel-arrow-color",e);const a=["top","middle","bottom"].includes(this.dataset.arrowPosition)?this.dataset.arrowPosition:"middle";this.classList.add(`dm-carousel-arrows-${a}`);const d={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide",heightMode:this.dataset.heightMode==="average"?"average":"crop"};if(this.dataset.height){const p=Number(this.dataset.height);Number.isFinite(p)&&p>0&&(d.height=p)}if(this.dataset.animationDuration){const p=parseInt(this.dataset.animationDuration,10);Number.isFinite(p)&&(d.animationDuration=p)}this.dataset.animationEasing&&(d.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(p=>p.cloneNode(!0)),this._dmCarouselOpts=d),this._dmCarousel=Domma.elements.carousel(this,d),ie(this)}),C.find(".dm-countdown").each(function(){const n={autoStart:!0},o=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)?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(o);n.format=a?"hh:mm:ss":o||(n.duration>=36e5?"hh:mm:ss":"mm:ss");const d=this,p=r=>String(r).padStart(2,"0"),i=r=>{const g=Math.max(0,Math.floor(r/1e3)),b=d.querySelector(".dm-timer-time");b&&(b.textContent=`${p(Math.floor(g/86400))}:${p(Math.floor(g%86400/3600))}:${p(Math.floor(g%3600/60))}:${p(g%60)}`)};a&&(n.onTick=i),Domma.elements.timer(this,n),a&&i(n.duration||0)}),C.find("[data-tooltip]:not(.dm-range-slider)").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),C.find(".dm-progression").each(function(){const n=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}),o=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",s=Domma.elements.progression(this,{layout:o,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...n.length?{items:n}:{}});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 d=n[a];if(!d)return;const p=e.querySelector(".dm-progression-title");if(d.icon&&p){const r=document.createElement("span");r.dataset.icon=d.icon,r.style.cssText="display:inline-flex;vertical-align:middle;margin-right:.4em",r.setAttribute("aria-hidden","true"),p.prepend(r)}const i=t[d.status];if(i&&p){const r=document.createElement("span");r.className=`badge badge-${i.tone} cms-timeline-status`,r.style.cssText="display:inline-flex;align-items:center;gap:.35em;margin:.4rem 0";const g=document.createElement("span");g.dataset.icon=i.icon,g.dataset.iconSize="14",g.setAttribute("aria-hidden","true"),r.append(g,document.createTextNode(i.label)),p.after(r)}})}Domma.icons.scan(this),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 o=n.dataset.revealMode||"stagger",s=n.dataset.revealAnimation||"slide-up",t=parseInt(n.dataset.revealDuration,10)||400,e=parseInt(n.dataset.revealStagger,10)||60,a=parseInt(n.dataset.revealDelay,10)||0,d=n.dataset.revealDirection==="rtl",p=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)"};p.forEach((r,g)=>{r.style.opacity="0",r.style.transform=i[s]||"",r.style.transition=`opacity ${t}ms ease, transform ${t}ms ease`;const b=d?p.length-1-g:g;r.style.transitionDelay=o==="stagger"?`${a+b*e}ms`:`${a}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const r=new IntersectionObserver(g=>{g.forEach(b=>{b.isIntersecting&&(b.target.offsetWidth,b.target.style.opacity="1",b.target.style.transform="none",r.unobserve(b.target))})},{threshold:.1});p.forEach(g=>r.observe(g))}))}),C.find(".card[data-collapsible]").each(function(){const n=this.querySelector(".card-header");n&&n.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),C.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const n=this.dataset.soTarget,o=document.getElementById(n);if(!o)return;const s=E.slideover({title:o.dataset.soTitle||"",size:o.dataset.soSize||"md",position:o.dataset.soPosition||"right"});o.style.display="",s.setContent(o),s.open()})})}if(typeof $.setup=="function"){const n=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(o=>{try{const s=W(o.dataset.config);Object.assign(n,s)}catch{}}),Object.keys(n).length>0){const o={};for(const[s,t]of Object.entries(n)){const e=t?.events?.click,{confirm:a,toast:d,alert:p,prompt:i,...r}=e||{};a||d||p||i?($(s).on("click",async function(b){if(b.preventDefault(),a&&!await E.confirm(a))return;let u=null;if(!(i&&(u=await E.prompt(i,{inputPlaceholder:r.promptPlaceholder||"",inputValue:r.promptDefault||""}),u===null))){if(r.target){const y=$(r.target);r.toggleClass&&y.toggleClass(r.toggleClass),r.addClass&&y.addClass(r.addClass),r.removeClass&&y.removeClass(r.removeClass),u!==null&&(r.setText&&y.text(u),r.setVal&&y.val(u),r.setAttr&&y.attr(r.setAttr,u))}r.href&&(window.location.href=r.href),d&&E.toast(d,{type:r.toastType||"success"}),p&&E.alert(p)}}),Object.keys(r).length&&(o[s]={...t,events:{...t.events,click:r}})):o[s]=t}$.setup(o)}}C.length&&R(C.get(0))}),window.wireCTAButtons=R;function R(m){m.querySelectorAll(".dm-cta-trigger").forEach(c=>{c.__ctaWired||(c.__ctaWired=!0,c.addEventListener("click",async()=>{const x=c.dataset.action,v=c.dataset.entry,k=c.dataset.confirm;let C=S.get("auth_token");if(!C){E.toast("Please log in to perform this action.",{type:"warning"});return}if(k&&!await E.confirm(k))return;const n=Array.from(c.childNodes).map(s=>s.cloneNode(!0));c.disabled=!0,c.textContent="Running\u2026";const o=s=>fetch(`/api/actions/${x}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(c.dataset.scope?{entryId:v,scope:c.dataset.scope}:{entryId:v})});try{let s=await o(C);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:d}=await a.json();S.set("auth_token",d),C=d,s=await o(C)}}}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{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 v(C,n){const o=document.createElement("div");o.className="dm-collection-display dm-collection-empty";const s=document.createElement("p");if(n){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=C;return o.appendChild(s),o}function k(C,n){const o=document.createElement("template");o.innerHTML=n||"",C.replaceWith(o.content)}c.forEach(async C=>{const n=C.dataset.collectionAttrs;if(!n)return;if(!x){C.replaceChildren(v("",!0));return}const o=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,t=await o(s);if(t.status===401){const a=S.get("auth_refresh_token");if(a){const d=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:a})});if(d.ok){const{token:p}=await d.json();S.set("auth_token",p),s=p,t=await o(s)}}}if(!t.ok){const{error:a}=await t.json().catch(()=>({}));C.replaceChildren(v(a||"Unable to load entries.",!1));return}const{html:e}=await t.json();k(C,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{C.replaceChildren(v("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),R(document.body)},100)})(),(function(){const c=document.querySelectorAll("[data-collection-table]");!c.length||typeof T>"u"||!T.create||c.forEach(x=>{let v;try{v=W(x.dataset.payload)}catch{return}const{columns:k,rows:C,search:n,sortable:o,exportable:s,pageSize:t,empty:e,ctaConfig:a,slug:d,exportCfg:p}=v;if(!k?.length)return;if(a){const g=k.findIndex(b=>b.key==="_cta");g!==-1&&(k[g]={key:"_cta",title:"",render:(b,u)=>{const y=document.createElement("button");if(y.className=`btn btn-${a.style||"primary"} dm-cta-trigger`,y.dataset.action=a.action||"",a.scope&&(y.dataset.scope=a.scope),y.dataset.entry=u._entryId||"",a.confirm&&(y.dataset.confirm=a.confirm),a.icon){const h=document.createElement("span");h.dataset.icon=a.icon,y.appendChild(h),y.appendChild(document.createTextNode(" "))}return y.appendChild(document.createTextNode(a.label||"Run")),y}})}const i="col-table-"+Math.random().toString(36).slice(2,7),r=document.createElement("div");r.id=i,x.replaceChildren(r),T.create("#"+i,{data:C,columns:k,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&R(r),x._dmTable={columns:k,rows:C,slug:d,exportCfg:p,render(g){T.create("#"+i,{data:g,columns:k,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&R(r)}}})})(),(function(){const c=document.querySelectorAll("[data-form-inline]");if(!c.length||typeof F>"u")return;function x(o){const s=o&&o.logic;return!!(s&&(s.visibility||s.requirement))}function v(o,s){const t={};return(o||[]).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)),d=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":a?"chooser":e.type,p={...e.formConfig||{}};p.span==="full"&&s&&(p.span=s);const i={type:d,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},...p}};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]=e.type==="file"?ve(e,i):i}),t}function k(o){const s={};return(o||[]).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 C(o,s){(s||[]).forEach(t=>{if(t.type!=="date"||!t.name)return;const e=o.querySelector(`[name="${t.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function n(o,s,t){let e=o.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",o.appendChild(e)),e.textContent=s,e.style.cssText=t?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}c.forEach(o=>{let s;try{s=W(o.dataset.formInline)}catch{return}o.querySelector(":scope > .cms-form-embed-summary")?.remove();const t=s.fields||[],e=s.settings||{},a=Number(e.columns)===3?3:2,d=e.layout||"grid",p=t.some(h=>h.type==="page-break"),i=new Map;Se(o,t);const r=async h=>{if(o.dataset.triggerBlocked==="true"){const f=o._formLogicRuntime,w=f&&f.submitBlock?f.submitBlock().reasons:[];n(o,w.join(" ")||"This form cannot be submitted yet.",!0);const A=new Error("Blocked by a form trigger");throw A.formSubmitFailed=!0,A}try{const f=o.querySelector(".fb-form-honeypot"),w=f?.querySelector('input[type="text"]')||null,A=f?.querySelector('[name="_t"]')||null,I=Object.assign({},h);delete I._t,f?.dataset.hpName&&delete I[f.dataset.hpName],w!==null&&(I._hp=w.value),A!==null&&(I._t=A.value);const N=new Map(i);oe(o,N);const O=we(I,t,N),B=await H.post(`/api/forms/submit/${s.slug}`,O.files.size?Ce(O.values,O.files).body:O.values);if(B&&B.error){n(o,B.error,!0);const M=new Error(B.error);throw M.formSubmitFailed=!0,M}if(B?.redirect){window.location.href=B.redirect;return}for(ye(o);o.firstChild;)o.removeChild(o.firstChild);n(o,B?.message||e.successMessage||"Thank you for your submission.",!1)}catch(f){throw f.formSubmitFailed||n(o,f.message||"Submission failed. Please try again.",!0),f}},g=Date.now();function b(h){const f=h.querySelector("form");if(!f||f.querySelector(".fb-form-honeypot"))return;const w=document.createElement("div");w.className="fb-form-honeypot",w.setAttribute("aria-hidden","true");const A=document.createElement("input");A.name=f.querySelector('[name="website"]')?"_website":"website",w.dataset.hpName=A.name,A.type="text",A.tabIndex=-1,A.autocomplete="url",A.placeholder="https://",w.appendChild(A);const I=document.createElement("input");I.name="_t",I.type="hidden",I.value=g,w.appendChild(I),f.appendChild(w)}function u(h){if(!window.FormLogicEngine||!t.some(w=>w.logic||Array.isArray(w.triggers)&&w.triggers.length))return null;const f=new window.FormLogicEngine.FormLogicRuntime(s,o,h?{baseValues:h}:void 0);if(f.init(),o._formLogicRuntime=f,o._formLogicObserved)return f;if(o._formLogicObserved=!0,o.parentNode&&typeof MutationObserver<"u"){const w=new MutationObserver(function(A){for(const I of A)for(const N of I.removedNodes)if(N===o||N.nodeType===1&&N.contains&&N.contains(o)){o._formLogicRuntime&&o._formLogicRuntime.destroy(),w.disconnect();return}});w.observe(o.parentNode,{childList:!0,subtree:!1}),o._formLogicObserver=w}return f}let y=!1;if(o.addEventListener("dm-form-end",h=>{const f=h.detail?.validation;if(f&&!f.valid){Z(o,f.missing),ee(f);return}y||(y=!0,r(h.detail?.values||{}).catch(()=>{}).finally(()=>{y=!1}))}),p&&F.wizard){const h=fe(t,s.title),f=h.map(M=>({title:M.title,description:M.description,fields:v(M.fields,a)})),w=h.map(M=>M.fields);let A=0,I=null,N=null;const O=M=>{const z=window.FormLogicEngine;if(!z||!z.validateFields)return;const l=z.validateFields(o,w[A]||[]);l.valid||(M.preventDefault(),M.stopImmediatePropagation(),Z(o,l.missing),ee(l))},B=()=>{const M=w[A]||t;ge(o,e),te(o,e,M),C(o,M),Q(o,M),be(o,i),e.honeypot!==!1&&b(o),I&&I.destroy(),I=u(N?.getStepData?N.getStepData():void 0),o.querySelectorAll(".wizard-next, .wizard-submit").forEach(z=>z.addEventListener("click",O,!0))};N=F.wizard(o,{schema:{steps:f},layout:d,columns:a,finishText:e.submitText||"Finish",onSubmit:r,onStepChange:M=>{oe(o,i),A=M,setTimeout(B,0)}}),o.addEventListener("dm-form-trigger",M=>{if(M.detail?.type!=="goto-step"||!N?.goToStep)return;const z=Number(M.detail.action.step);!Number.isFinite(z)||z<0||z>=f.length||z!==A&&N.goToStep(z)}),Promise.resolve(N).then(B)}else if(F.render){const h=F.render(o,v(t,a),k(t),{submitText:e.submitText||"Submit",layout:d,columns:a,onSubmit:r});Promise.resolve(h).then(function(){he(o,e,d),te(o,e,t),C(o,t),Q(o,t),e.honeypot!==!1&&b(o),u()})}})})();function xe(m,c){const x=document.body,v=x.classList,k=new Set((x.dataset.railSite||"").split(" ").filter(Boolean)),C=()=>window.matchMedia("(min-width: 993px)").matches;m.querySelectorAll(".navbar-link, .navbar-dropdown-toggle").forEach(a=>{const d=a.textContent.trim();d&&(a.setAttribute("title",d),a.querySelector("svg, [data-icon]")||(a.dataset.initial=d.charAt(0).toUpperCase()))});const n=document.createElement("button");n.type="button",n.className="dm-nav-collapse-toggle";const o=()=>{const a=v.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 d=c?"chevrons-left":"chevrons-right",p=c?"chevrons-right":"chevrons-left";n.innerHTML=`<span data-icon="${a?d:p}"></span>`,window.Domma&&Domma.icons&&Domma.icons.scan(n)},s=a=>{v.toggle("dm-vnav-collapsed",a),S.set("nav_collapsed",a),o()};n.addEventListener("click",()=>s(!v.contains("dm-vnav-collapsed"))),o();const t=m.querySelector(".navbar-container")||m;t.insertBefore(n,t.firstChild?.nextSibling||null);const e=a=>{v.toggle("dm-vnav-autohide",a),v.toggle("dm-vnav-collapsible",!a&&k.has("collapsible")),a?v.remove("dm-vnav-collapsed"):k.has("collapsible")&&S.get("nav_collapsed")===!0&&v.add("dm-vnav-collapsed"),m.classList.remove("dm-nav-revealed"),o()};m.addEventListener("pointerdown",a=>{v.contains("dm-vnav-autohide")&&a.pointerType!=="mouse"&&!m.classList.contains("dm-nav-revealed")&&(m.classList.add("dm-nav-revealed"),a.preventDefault())}),document.addEventListener("pointerdown",a=>{m.contains(a.target)||m.classList.remove("dm-nav-revealed")}),window.E&&typeof E.contextMenu=="function"&&E.contextMenu(m,{items:()=>{const a=v.contains("dm-vnav-autohide"),d=v.contains("dm-vnav-collapsed"),p=S.has("nav_autohide")||S.has("nav_collapsed");return[{header:"Menu"},{label:d?"Expand menu":"Collapse to icons",icon:d?"maximize":"minimize",disabled:!C()||a||!k.has("collapsible"),action:()=>s(!d)},{label:a?"Keep on screen":"Hide when not in use",icon:a?"eye":"eye-off",disabled:!C(),action:()=>{S.set("nav_autohide",!a),e(!a)}},{type:"divider"},{label:"Reset to the site's setting",icon:"refresh",disabled:!p,action:()=>{S.remove("nav_autohide"),S.remove("nav_collapsed"),e(k.has("autohide")),v.toggle("dm-vnav-collapsed",!k.has("autohide")&&k.has("start-collapsed")),o()}}]}})}function ke(m){m.classList.add("dm-nav-scroll-hide");let c=window.scrollY,x=!1;window.addEventListener("scroll",()=>{x||(x=!0,requestAnimationFrame(()=>{x=!1;const v=window.scrollY,k=m.matches(":focus-within")||m.querySelector(".navbar-dropdown.open, .navbar-collapse.show");v>c&&v>m.offsetHeight&&!k?m.classList.add("dm-nav-scrolled-away"):v<c&&m.classList.remove("dm-nav-scrolled-away"),c=v}))},{passive:!0})}function Le(m){let c={};try{c=JSON.parse(m.dataset.contents||"{}")}catch{}const x=m.closest(".site-main"),v=document.querySelector(".site-content"),k=new Set(String(c.exclude||"").split(",").map(i=>i.trim().toLowerCase()).filter(Boolean)),C=i=>!i.closest(".hero, .card, .accordion, .tabs, figure, #site-sidebar"),n=i=>(v?[...v.querySelectorAll(i)]:[]).filter(C).filter(r=>!k.has(r.textContent.trim().toLowerCase())),o=c.headings==="h2"?n("h2"):c.headings==="h3"?n("h3"):c.headings==="h2h3"?n("h2, h3"):n("h2").length?n("h2"):n("h3");if(!o.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",c.title||"Page contents"),c.hover&&(t.dataset.menuHover=c.hover),c.selected&&(t.dataset.menuActive=c.selected),c.accent&&t.style.setProperty("--dm-menu-highlight",c.accent.startsWith("#")?c.accent:`var(--dm-${c.accent})`),c.font&&(t.style.fontFamily=c.font),c.fontSize&&(t.style.fontSize=c.fontSize),c.fontWeight&&(t.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),t.appendChild(e);const d=document.createElement("ol");d.className="dm-contents-list"+(c.numbered?" is-numbered":"");const p=[];for(const i of o){if(!i.id){const b=(i.textContent||"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"section";let u=b;for(let y=2;s.has(u);y++)u=`${b}-${y}`;s.add(u),i.id=u}const r=document.createElement("li");r.className=`dm-contents-item dm-contents-${i.tagName.toLowerCase()}`;const g=document.createElement("a");g.href=`#${i.id}`,g.textContent=i.textContent.trim(),r.appendChild(g),d.appendChild(r),p.push([i,g])}if(t.appendChild(d),m.replaceChildren(t),c.scrollSpy!==!1&&"IntersectionObserver"in window){const i=new Map,r=()=>{const b=p.find(([u])=>i.get(u))||null;p.forEach(([,u])=>{const y=b&&u===b[1];u.classList.toggle("is-active",!!y),y?u.setAttribute("aria-current","location"):u.removeAttribute("aria-current")})},g=new IntersectionObserver(b=>{b.forEach(u=>i.set(u.target,u.isIntersecting)),r()},{rootMargin:"-80px 0px -60% 0px"});p.forEach(([b])=>g.observe(b))}if(x?.classList.contains("contents-collapsible")){const i=document.createElement("button");i.type="button",i.className="dm-contents-toggle";const r=()=>{const g=x.classList.contains("contents-collapsed");i.setAttribute("aria-expanded",String(!g)),i.setAttribute("aria-label",g?"Show the contents":"Hide the contents"),i.title=i.getAttribute("aria-label"),i.innerHTML=`<span data-icon="${g?"list":"x"}"></span>`,window.Domma?.icons&&Domma.icons.scan(i)};i.addEventListener("click",()=>{S.set("contents_collapsed",x.classList.toggle("contents-collapsed")),r()}),r(),e.insertBefore(i,e.firstChild)}if(x?.classList.contains("contents-floating")&&c.movable){t.classList.add("is-movable");const i=S.get("contents_position");i&&Number.isFinite(i.x)&&Object.assign(m.style,{left:`${i.x}px`,top:`${i.y}px`,right:"auto",bottom:"auto"}),e.addEventListener("pointerdown",r=>{if(r.target.closest("button"))return;const g=m.getBoundingClientRect(),b=r.clientX-g.left,u=r.clientY-g.top,y=f=>{const w=Math.max(0,Math.min(window.innerWidth-g.width,f.clientX-b)),A=Math.max(0,Math.min(window.innerHeight-40,f.clientY-u));Object.assign(m.style,{left:`${w}px`,top:`${A}px`,right:"auto",bottom:"auto"})},h=()=>{document.removeEventListener("pointermove",y),S.set("contents_position",{x:parseInt(m.style.left,10),y:parseInt(m.style.top,10)})};document.addEventListener("pointermove",y),document.addEventListener("pointerup",h,{once:!0})})}window.E&&typeof E.contextMenu=="function"&&E.contextMenu(m,{items:()=>{const i=x?.classList.contains("contents-collapsible"),r=x?.classList.contains("contents-collapsed"),g=p.find(([,b])=>b.classList.contains("is-active"));return[{header:"Contents"},...i?[{label:r?"Show the contents":"Collapse to a button",icon:r?"list":"minimize",action:()=>m.querySelector(".dm-contents-toggle")?.click()}]:[],{label:"Copy a link to this section",icon:"link",disabled:!g,action:async()=>{const b=`${location.origin}${location.pathname}#${g[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")&&(m.addEventListener("pointerdown",i=>{i.pointerType!=="mouse"&&!m.classList.contains("is-revealed")&&(m.classList.add("is-revealed"),i.preventDefault())}),document.addEventListener("pointerdown",i=>{m.contains(i.target)||m.classList.remove("is-revealed")}))}
1
+ import{applyMobileMenu as se,initCarouselPopup as ie}from"/public/js/mobile-ui.mjs?v=20261016-mobile-options";import{installSmoothCarets as re}from"/public/js/smooth-caret.js";import{decodeBase64Json as W}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as U,applyColour as G,applyPill as ce,applyFloat as le,applyHighlight as V,applyItemHighlight as de,itemHighlightAttrs as me,isCurrentItem as K,menuAlign as ue,applyClasses as P,cssClasses as Y,applyEntrance as J}from"/public/js/menu-decor.mjs?v=20261014-menu-entrance";import{initFloatingMenus as pe}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as Q,markInvalidFields as Z,showValidationPopup as ee}from"/public/js/forms.js?v=20261025-forms-files-total";import{buildWizardSteps as fe}from"/public/js/form-steps.mjs";import{applyFormClasses as te,applySubmitButtons as he,applyWizardButtons as ge}from"/public/js/form-submit.mjs";import{captureFileFields as oe,destroyDropzones as ye,fileBlueprint as ve,restoreFileFields as be,setFileLimits as we,splitFiles as Ce,submitBody as Se,totalTooLarge as xe,wireFileFields as ke}from"/public/js/form-files.mjs";document.addEventListener("click",m=>{const c=m.target?.closest?.(".dm-banner__dismiss");c&&(m.preventDefault(),c.closest(".dm-banner")?.remove())}),$(()=>{re(document);const m=window.__CMS_NAV__||{},c=window.__CMS_SITE__||{};if(c.autoTheme?.enabled){let o=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>=o(n.dayStart)&&a<o(n.nightStart)?n.dayTheme:n.nightTheme},t=function(e){const a=n.apply&&n.apply[e]||{theme:e,custom:""};Domma.theme.set(a.theme);for(const d of[...document.body.classList])d.startsWith("dm-custom-")&&document.body.classList.remove(d);a.custom&&document.body.classList.add(a.custom)};const n=c.autoTheme;t(s()),setInterval(()=>t(s()),6e4)}const x=$("#site-navbar");if(x.length&&m.brand){let s=function(u,y=0){return Array.isArray(u)?u.filter(h=>!(h&&(h.type==="spacer"||h.type==="theme-switcher"))).filter(h=>!(h&&h.type==="separator"&&y===0)).map(h=>h&&h.type==="separator"?{divider:!0}:Array.isArray(h?.items)?{...h,items:s(h.items,y+1)}:h):u};const n={...m.brand},o=n.size&&n.size!=="md"?` navbar-brand-${n.size}`:"";if(n.logo||n.icon||n.tagline){let u="";n.logo?u+=`<img src="${n.logo}" class="navbar-brand-logo" alt="${n.text||""}">`:n.icon&&(u+=`<span data-icon="${n.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),n.text&&(u+=`<span class="navbar-brand-text${o}">${n.text}</span>`),n.tagline&&(u+=`<small class="navbar-brand-tagline">${n.tagline}</small>`),n.html=u}else o&&n.text&&(n.html=`<span class="navbar-brand-text${o}">${n.text}</span>`);const t=s(m.items||[]),e=m.position==="floating",a=m.orientation==="vertical";x.get(0).querySelector(":scope > .cms-nojs-links")?.remove(),Domma.elements.navbar("#site-navbar",{brand:n,items:t,variant:m.variant||"dark",position:e?"static":m.position||"sticky",collapsible:!0,appearOnHover:!!m.appearOnHover}),Domma.icons.scan("#site-navbar");const d=x.get(0);se(d,m.mobile);const p=()=>{if(!a)return;const y=d.querySelector('a[aria-current="page"]')?.closest(".navbar-dropdown");y&&!y.classList.contains("open")&&y.querySelector(":scope > .navbar-dropdown-toggle")?.click()};d.classList.add(`dm-nav-align-${ue(m)||"centre"}`),a&&x.get(0).classList.add("dm-nav-vertical",m.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left");const i=m.dock||{};a&&!e&&Le(d,m.side==="right"),!a&&i.hideOnScroll&&$e(d),V(d,m),J(d,m),P(d,m.class),e&&(le(x.get(0),m),pe(x.get(0).parentNode||document));const r=d.querySelector(".navbar-toggle"),g=d.querySelector(".navbar-collapse");if(r&&g){let u=null;const y=()=>{g.classList.contains("show")&&r.click()},h=()=>{u&&(u.remove(),u=null),document.body.classList.remove("dm-drawer-open")},f=()=>{u||(u=document.createElement("div"),u.className="dm-navbar-backdrop",u.addEventListener("click",y),document.body.appendChild(u),document.body.classList.add("dm-drawer-open"))};r.addEventListener("click",()=>{requestAnimationFrame(()=>{g.classList.contains("show")?(p(),f()):h()})}),g.addEventListener("click",w=>{w.target.closest("a")&&requestAnimationFrame(h)}),document.addEventListener("keydown",w=>{w.key==="Escape"&&g.classList.contains("show")&&y()})}const b=d.querySelector(".navbar-nav");if(b){const u=Array.from(b.children);let y=0;for(const h of m.items||[])if(!(h&&h.type==="separator")){if(h&&h.type==="spacer"){const f=document.createElement("li"),w=["xs","sm","md","lg","xl","flex"].includes(h.size)?h.size:"md";f.className=`dm-menu-spacer dm-menu-spacer--${w}`,f.setAttribute("aria-hidden","true"),b.insertBefore(f,u[y]||null);continue}if(h&&h.type==="theme-switcher"){const f=document.createElement("li");f.className="dm-menu-theme-switcher";const w=document.createElement("span");w.dataset.themeSwitcher="menu",w.dataset.position="inline",f.appendChild(w),b.insertBefore(f,u[y]||null);continue}y++}window.CMSThemeSwitcher?.scan(b)}d.querySelectorAll("[data-path]").forEach(u=>{const y=u.dataset.path.split(".").map(Number),h=y.length>1,f=y.reduce((q,I)=>q?.items?.[I],{items:t});if(!f)return;f.colour&&G(u,f.colour),f.pill&&!h&&ce(u,f.pill),de(u,f),P(u.closest("li"),f.class);const w=U(f.badge);w&&u.appendChild(w),K(f.url,location.pathname)&&(u.classList.add("active"),u.setAttribute("aria-current","page"))}),p()}const v=$("#site-footer");if(v.length){let e=function(l){return String(l??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},a=function(l){return e(l).replace(/'/g,"&#39;")},d=function(l){if(l&&l.type==="separator")return'<li class="footer-sep"><hr></li>';if(l&&l.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(l.size)?l.size:"md"}" aria-hidden="true"></li>`;if(l&&l.type==="theme-switcher")return'<li class="dm-menu-theme-switcher"><span data-theme-switcher="menu" data-position="inline"></span></li>';const D=a(l.url||"#"),A=e(l.text||""),M=Array.isArray(l.items)&&l.items.length?`<ul>${l.items.map(d).join("")}</ul>`:"",j=me(l),ne=[j.hover&&` data-menu-item-hover="${a(j.hover)}"`,j.active&&` data-menu-item-active="${a(j.active)}"`].filter(Boolean).join(""),ae=j.accent?` style="--dm-menu-highlight:${a(j.accent)}"`:"",X=Y(l.class).join(" ");return`<li${X?` class="${a(X)}"`:""}><a href="${D}"${ne}${ae}>${A}</a>${M}</li>`},i=function(){if(!c.footer)return"";const l=Object.keys(o).filter(A=>n[A]);if(!l.length)return"";const D=Y(c.footer.socialClass).join(" ");return`<div class="footer-social">${l.map(A=>{const{label:M,svg:j}=o[A];return`<a href="${n[A]}" target="_blank" rel="noopener noreferrer" aria-label="${M}" class="footer-social-link${D?` ${a(D)}`:""}">${j}</a>`}).join("")}</div>`},u=function(l){if(!l||l.type==="separator"||l.type==="spacer")return"";if(l.type==="theme-switcher")return`<li class="cms-footer-col">${d(l).replace(/^<li[^>]*>|<\/li>$/g,"")}</li>`;const D=Array.isArray(l.items)&&l.items.length?`<ul>${l.items.map(d).join("")}</ul>`:"",A=b(l)?`<span class="cms-footer-col-title">${e(l.text||"")}</span>`:`<a class="cms-footer-col-title" href="${a(l.url||"#")}">${e(l.text||"")}</a>`,M=["cms-footer-col",...Y(l.class)].join(" ");return`<li class="${a(M)}">${A}${D}</li>`},f=function(l,D,A=!1){return(l||[]).forEach(M=>{M&&(M.type==="separator"||M.type==="spacer"||M.type==="theme-switcher")||(A&&b(M)||D.push(M),Array.isArray(M.items)&&M.items.length&&f(M.items,D))}),D};const n=c.social||{},o={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:""},p=(()=>{const l=c.cookieConsent;if(!l||l.showSettingsLink===!1)return"";const D=A=>(A||[]).some(M=>M&&(M.url==="#cookie-settings"||D(M.items)));return D(t.primary)||D(t.legal)?"":`<li class="cms-cookie-settings"><a href="#cookie-settings">${e(l.settingsLinkText||"Cookie settings")}</a></li>`})(),r=["columns","row"].includes(c.footer?.layout)?c.footer.layout:"auto",g=r==="columns"||r==="auto"&&t.primary.some(l=>Array.isArray(l?.items)&&l.items.length),b=l=>g&&!l.url&&Array.isArray(l.items)&&l.items.length>0;if(g){const l=m.brand||{},D=l.text||c.title||"",A=l.logo?`<img src="${a(l.logo)}" class="cms-footer-brand-logo" alt="">`:l.icon?'<span class="cms-footer-brand-icon"></span>':"",M=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">${D||A?`<a class="cms-footer-brand-name" href="${a(l.url||"/")}">${A}<span>${e(D)}</span></a>`:""}${M?`<p class="cms-footer-blurb">${e(M)}</p>`:""}${i()}</div>`,t.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary footer-nav--columns" aria-label="Footer"><ul>${t.primary.map(u).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||p)&&(s+=`<nav class="footer-nav footer-nav--legal" aria-label="Legal"><ul>${t.legal.map(d).join("")}${p}</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(d).join("")}</ul></nav>`),(t.legal.length||p)&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${t.legal.map(d).join("")}${p}</ul></nav>`),s+=i();s+="</div>",v.get(0).querySelector(":scope > .cms-nojs-links")?.remove();const y=v.get(0).querySelector(":scope > .cms-footer-blocks");y?.remove(),v.html(s),y&&v.get(0).prepend(y),window.CMSThemeSwitcher?.scan(v.get(0));const h=v.get(0).querySelector(".cms-footer-brand-icon");h&&m.brand?.icon&&h.setAttribute("data-icon",m.brand.icon),P(v.get(0),c.footer?.class);const w=[...f(t.primary,[],!0),...f(t.legal,[])];v.get(0).querySelectorAll(".footer-nav a").forEach((l,D)=>{const A=w[D];if(!A)return;A.colour&&G(l,A.colour);const M=U(A.badge);M&&l.appendChild(M),K(A.url,location.pathname)&&(l.classList.add("is-active"),l.setAttribute("aria-current","page"))}),V(v.get(0).querySelector(".footer-nav--primary"),{highlight:t.primaryHighlight}),V(v.get(0).querySelector(".footer-nav--legal"),{highlight:t.legalHighlight}),J(v.get(0).querySelector(".footer-nav--primary"),{entrance:t.primaryEntrance}),J(v.get(0).querySelector(".footer-nav--legal"),{entrance:t.legalEntrance}),P(v.get(0).querySelector(".footer-nav--primary"),t.primaryClass),P(v.get(0).querySelector(".footer-nav--legal"),t.legalClass);const I=c.footer?.showMotionToggle!==!1,z=S.get("reduced_motion"),B=c.footer?.motionDefault,O=z!=null?!!z:B==="on"?!0:B==="off"?!1:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),k=c.footer?.motionControl==="checkbox",N=v.get(0).querySelector(".cms-footer-bottom")||v.get(0).querySelector(".footer-inner");if(I&&N){const l=document.createElement("input");l.type="checkbox",l.className=k?"footer-motion-checkbox":"form-switch-input",l.id="dm-motion-switch",l.checked=O,l.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const D=document.createElement("span");D.className=k?"footer-motion-label":"form-switch-label",D.textContent="Reduce motion";const A=document.createElement("label");A.className=k?"footer-motion-check":"form-switch footer-motion-switch",A.title="Reduce motion",A.appendChild(l),A.appendChild(D),N.appendChild(A)}}const L=document.getElementById("site-sidebar");L&&Ee(L),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const C=$(".page-body");if(C.length){C.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),C.find(".tabs").each(function(){Domma.elements.tabs(this)}),C.find(".carousel").each(function(){const n={primary:"--dm-primary",secondary:"--dm-secondary",accent:"--dm-primary",text:"--dm-text",surface:"--dm-surface",background:"--dm-background",success:"--dm-success",warning:"--dm-warning",danger:"--dm-danger",info:"--dm-info"},o=p=>{const i=String(p||"").trim();return i?n[i.toLowerCase()]?`var(${n[i.toLowerCase()]})`:CSS.supports("color",i)?i:"":""},s=o(this.dataset.background),t=o(this.dataset.arrowBackground),e=o(this.dataset.arrowColor);s&&(this.style.setProperty("--dm-carousel-background",s),this.classList.add("dm-carousel-custom-background")),t&&this.style.setProperty("--dm-carousel-arrow-background",t),e&&this.style.setProperty("--dm-carousel-arrow-color",e);const a=["top","middle","bottom"].includes(this.dataset.arrowPosition)?this.dataset.arrowPosition:"middle";this.classList.add(`dm-carousel-arrows-${a}`);const d={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide",heightMode:this.dataset.heightMode==="average"?"average":"crop"};if(this.dataset.height){const p=Number(this.dataset.height);Number.isFinite(p)&&p>0&&(d.height=p)}if(this.dataset.animationDuration){const p=parseInt(this.dataset.animationDuration,10);Number.isFinite(p)&&(d.animationDuration=p)}this.dataset.animationEasing&&(d.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(p=>p.cloneNode(!0)),this._dmCarouselOpts=d),this._dmCarousel=Domma.elements.carousel(this,d),ie(this)}),C.find(".dm-countdown").each(function(){const n={autoStart:!0},o=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)?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(o);n.format=a?"hh:mm:ss":o||(n.duration>=36e5?"hh:mm:ss":"mm:ss");const d=this,p=r=>String(r).padStart(2,"0"),i=r=>{const g=Math.max(0,Math.floor(r/1e3)),b=d.querySelector(".dm-timer-time");b&&(b.textContent=`${p(Math.floor(g/86400))}:${p(Math.floor(g%86400/3600))}:${p(Math.floor(g%3600/60))}:${p(g%60)}`)};a&&(n.onTick=i),Domma.elements.timer(this,n),a&&i(n.duration||0)}),C.find("[data-tooltip]:not(.dm-range-slider)").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),C.find(".dm-progression").each(function(){const n=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}),o=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",s=Domma.elements.progression(this,{layout:o,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...n.length?{items:n}:{}});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 d=n[a];if(!d)return;const p=e.querySelector(".dm-progression-title");if(d.icon&&p){const r=document.createElement("span");r.dataset.icon=d.icon,r.style.cssText="display:inline-flex;vertical-align:middle;margin-right:.4em",r.setAttribute("aria-hidden","true"),p.prepend(r)}const i=t[d.status];if(i&&p){const r=document.createElement("span");r.className=`badge badge-${i.tone} cms-timeline-status`,r.style.cssText="display:inline-flex;align-items:center;gap:.35em;margin:.4rem 0";const g=document.createElement("span");g.dataset.icon=i.icon,g.dataset.iconSize="14",g.setAttribute("aria-hidden","true"),r.append(g,document.createTextNode(i.label)),p.after(r)}})}Domma.icons.scan(this),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 o=n.dataset.revealMode||"stagger",s=n.dataset.revealAnimation||"slide-up",t=parseInt(n.dataset.revealDuration,10)||400,e=parseInt(n.dataset.revealStagger,10)||60,a=parseInt(n.dataset.revealDelay,10)||0,d=n.dataset.revealDirection==="rtl",p=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)"};p.forEach((r,g)=>{r.style.opacity="0",r.style.transform=i[s]||"",r.style.transition=`opacity ${t}ms ease, transform ${t}ms ease`;const b=d?p.length-1-g:g;r.style.transitionDelay=o==="stagger"?`${a+b*e}ms`:`${a}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const r=new IntersectionObserver(g=>{g.forEach(b=>{b.isIntersecting&&(b.target.offsetWidth,b.target.style.opacity="1",b.target.style.transform="none",r.unobserve(b.target))})},{threshold:.1});p.forEach(g=>r.observe(g))}))}),C.find(".card[data-collapsible]").each(function(){const n=this.querySelector(".card-header");n&&n.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),C.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const n=this.dataset.soTarget,o=document.getElementById(n);if(!o)return;const s=E.slideover({title:o.dataset.soTitle||"",size:o.dataset.soSize||"md",position:o.dataset.soPosition||"right"});o.style.display="",s.setContent(o),s.open()})})}if(typeof $.setup=="function"){const n=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(o=>{try{const s=W(o.dataset.config);Object.assign(n,s)}catch{}}),Object.keys(n).length>0){const o={};for(const[s,t]of Object.entries(n)){const e=t?.events?.click,{confirm:a,toast:d,alert:p,prompt:i,...r}=e||{};a||d||p||i?($(s).on("click",async function(b){if(b.preventDefault(),a&&!await E.confirm(a))return;let u=null;if(!(i&&(u=await E.prompt(i,{inputPlaceholder:r.promptPlaceholder||"",inputValue:r.promptDefault||""}),u===null))){if(r.target){const y=$(r.target);r.toggleClass&&y.toggleClass(r.toggleClass),r.addClass&&y.addClass(r.addClass),r.removeClass&&y.removeClass(r.removeClass),u!==null&&(r.setText&&y.text(u),r.setVal&&y.val(u),r.setAttr&&y.attr(r.setAttr,u))}r.href&&(window.location.href=r.href),d&&E.toast(d,{type:r.toastType||"success"}),p&&E.alert(p)}}),Object.keys(r).length&&(o[s]={...t,events:{...t.events,click:r}})):o[s]=t}$.setup(o)}}C.length&&R(C.get(0))}),window.wireCTAButtons=R;function R(m){m.querySelectorAll(".dm-cta-trigger").forEach(c=>{c.__ctaWired||(c.__ctaWired=!0,c.addEventListener("click",async()=>{const x=c.dataset.action,v=c.dataset.entry,L=c.dataset.confirm;let C=S.get("auth_token");if(!C){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 o=s=>fetch(`/api/actions/${x}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(c.dataset.scope?{entryId:v,scope:c.dataset.scope}:{entryId:v})});try{let s=await o(C);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:d}=await a.json();S.set("auth_token",d),C=d,s=await o(C)}}}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{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 v(C,n){const o=document.createElement("div");o.className="dm-collection-display dm-collection-empty";const s=document.createElement("p");if(n){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=C;return o.appendChild(s),o}function L(C,n){const o=document.createElement("template");o.innerHTML=n||"",C.replaceWith(o.content)}c.forEach(async C=>{const n=C.dataset.collectionAttrs;if(!n)return;if(!x){C.replaceChildren(v("",!0));return}const o=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,t=await o(s);if(t.status===401){const a=S.get("auth_refresh_token");if(a){const d=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:a})});if(d.ok){const{token:p}=await d.json();S.set("auth_token",p),s=p,t=await o(s)}}}if(!t.ok){const{error:a}=await t.json().catch(()=>({}));C.replaceChildren(v(a||"Unable to load entries.",!1));return}const{html:e}=await t.json();L(C,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{C.replaceChildren(v("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),R(document.body)},100)})(),(function(){const c=document.querySelectorAll("[data-collection-table]");!c.length||typeof T>"u"||!T.create||c.forEach(x=>{let v;try{v=W(x.dataset.payload)}catch{return}const{columns:L,rows:C,search:n,sortable:o,exportable:s,pageSize:t,empty:e,ctaConfig:a,slug:d,exportCfg:p}=v;if(!L?.length)return;if(a){const g=L.findIndex(b=>b.key==="_cta");g!==-1&&(L[g]={key:"_cta",title:"",render:(b,u)=>{const y=document.createElement("button");if(y.className=`btn btn-${a.style||"primary"} dm-cta-trigger`,y.dataset.action=a.action||"",a.scope&&(y.dataset.scope=a.scope),y.dataset.entry=u._entryId||"",a.confirm&&(y.dataset.confirm=a.confirm),a.icon){const h=document.createElement("span");h.dataset.icon=a.icon,y.appendChild(h),y.appendChild(document.createTextNode(" "))}return y.appendChild(document.createTextNode(a.label||"Run")),y}})}const i="col-table-"+Math.random().toString(36).slice(2,7),r=document.createElement("div");r.id=i,x.replaceChildren(r),T.create("#"+i,{data:C,columns:L,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&R(r),x._dmTable={columns:L,rows:C,slug:d,exportCfg:p,render(g){T.create("#"+i,{data:g,columns:L,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&R(r)}}})})(),(function(){const c=document.querySelectorAll("[data-form-inline]");if(!c.length||typeof F>"u")return;function x(o){const s=o&&o.logic;return!!(s&&(s.visibility||s.requirement))}function v(o,s){const t={};return(o||[]).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)),d=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":a?"chooser":e.type,p={...e.formConfig||{}};p.span==="full"&&s&&(p.span=s);const i={type:d,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},...p}};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]=e.type==="file"?ve(e,i):i}),t}function L(o){const s={};return(o||[]).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 C(o,s){(s||[]).forEach(t=>{if(t.type!=="date"||!t.name)return;const e=o.querySelector(`[name="${t.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function n(o,s,t){let e=o.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",o.appendChild(e)),e.textContent=s,e.style.cssText=t?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}c.forEach(o=>{let s;try{s=W(o.dataset.formInline)}catch{return}o.querySelector(":scope > .cms-form-embed-summary")?.remove();const t=s.fields||[],e=s.settings||{},a=Number(e.columns)===3?3:2,d=e.layout||"grid",p=t.some(h=>h.type==="page-break"),i=new Map;we(o,s.fileLimits,i),ke(o,t);const r=async h=>{if(o.dataset.triggerBlocked==="true"){const f=o._formLogicRuntime,w=f&&f.submitBlock?f.submitBlock().reasons:[];n(o,w.join(" ")||"This form cannot be submitted yet.",!0);const q=new Error("Blocked by a form trigger");throw q.formSubmitFailed=!0,q}try{const f=o.querySelector(".fb-form-honeypot"),w=f?.querySelector('input[type="text"]')||null,q=f?.querySelector('[name="_t"]')||null,I=Object.assign({},h);delete I._t,f?.dataset.hpName&&delete I[f.dataset.hpName],w!==null&&(I._hp=w.value),q!==null&&(I._t=q.value);const z=new Map(i);oe(o,z);const B=Ce(I,t,z),O=xe(o,B.files);if(O){n(o,O,!0);const N=new Error(O);throw N.formSubmitFailed=!0,N}const k=await H.post(`/api/forms/submit/${s.slug}`,B.files.size?Se(B.values,B.files).body:B.values);if(k&&k.error){n(o,k.error,!0);const N=new Error(k.error);throw N.formSubmitFailed=!0,N}if(k?.redirect){window.location.href=k.redirect;return}for(ye(o);o.firstChild;)o.removeChild(o.firstChild);n(o,k?.message||e.successMessage||"Thank you for your submission.",!1)}catch(f){throw f.formSubmitFailed||n(o,f.message||"Submission failed. Please try again.",!0),f}},g=Date.now();function b(h){const f=h.querySelector("form");if(!f||f.querySelector(".fb-form-honeypot"))return;const w=document.createElement("div");w.className="fb-form-honeypot",w.setAttribute("aria-hidden","true");const q=document.createElement("input");q.name=f.querySelector('[name="website"]')?"_website":"website",w.dataset.hpName=q.name,q.type="text",q.tabIndex=-1,q.autocomplete="url",q.placeholder="https://",w.appendChild(q);const I=document.createElement("input");I.name="_t",I.type="hidden",I.value=g,w.appendChild(I),f.appendChild(w)}function u(h){if(!window.FormLogicEngine||!t.some(w=>w.logic||Array.isArray(w.triggers)&&w.triggers.length))return null;const f=new window.FormLogicEngine.FormLogicRuntime(s,o,h?{baseValues:h}:void 0);if(f.init(),o._formLogicRuntime=f,o._formLogicObserved)return f;if(o._formLogicObserved=!0,o.parentNode&&typeof MutationObserver<"u"){const w=new MutationObserver(function(q){for(const I of q)for(const z of I.removedNodes)if(z===o||z.nodeType===1&&z.contains&&z.contains(o)){o._formLogicRuntime&&o._formLogicRuntime.destroy(),w.disconnect();return}});w.observe(o.parentNode,{childList:!0,subtree:!1}),o._formLogicObserver=w}return f}let y=!1;if(o.addEventListener("dm-form-end",h=>{const f=h.detail?.validation;if(f&&!f.valid){Z(o,f.missing),ee(f);return}y||(y=!0,r(h.detail?.values||{}).catch(()=>{}).finally(()=>{y=!1}))}),p&&F.wizard){const h=fe(t,s.title),f=h.map(k=>({title:k.title,description:k.description,fields:v(k.fields,a)})),w=h.map(k=>k.fields);let q=0,I=null,z=null;const B=k=>{const N=window.FormLogicEngine;if(!N||!N.validateFields)return;const l=N.validateFields(o,w[q]||[]);l.valid||(k.preventDefault(),k.stopImmediatePropagation(),Z(o,l.missing),ee(l))},O=()=>{const k=w[q]||t;ge(o,e),te(o,e,k),C(o,k),Q(o,k),be(o,i),e.honeypot!==!1&&b(o),I&&I.destroy(),I=u(z?.getStepData?z.getStepData():void 0),o.querySelectorAll(".wizard-next, .wizard-submit").forEach(N=>N.addEventListener("click",B,!0))};z=F.wizard(o,{schema:{steps:f},layout:d,columns:a,finishText:e.submitText||"Finish",onSubmit:r,onStepChange:k=>{oe(o,i),q=k,setTimeout(O,0)}}),o.addEventListener("dm-form-trigger",k=>{if(k.detail?.type!=="goto-step"||!z?.goToStep)return;const N=Number(k.detail.action.step);!Number.isFinite(N)||N<0||N>=f.length||N!==q&&z.goToStep(N)}),Promise.resolve(z).then(O)}else if(F.render){const h=F.render(o,v(t,a),L(t),{submitText:e.submitText||"Submit",layout:d,columns:a,onSubmit:r});Promise.resolve(h).then(function(){he(o,e,d),te(o,e,t),C(o,t),Q(o,t),e.honeypot!==!1&&b(o),u()})}})})();function Le(m,c){const x=document.body,v=x.classList,L=new Set((x.dataset.railSite||"").split(" ").filter(Boolean)),C=()=>window.matchMedia("(min-width: 993px)").matches;m.querySelectorAll(".navbar-link, .navbar-dropdown-toggle").forEach(a=>{const d=a.textContent.trim();d&&(a.setAttribute("title",d),a.querySelector("svg, [data-icon]")||(a.dataset.initial=d.charAt(0).toUpperCase()))});const n=document.createElement("button");n.type="button",n.className="dm-nav-collapse-toggle";const o=()=>{const a=v.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 d=c?"chevrons-left":"chevrons-right",p=c?"chevrons-right":"chevrons-left";n.innerHTML=`<span data-icon="${a?d:p}"></span>`,window.Domma&&Domma.icons&&Domma.icons.scan(n)},s=a=>{v.toggle("dm-vnav-collapsed",a),S.set("nav_collapsed",a),o()};n.addEventListener("click",()=>s(!v.contains("dm-vnav-collapsed"))),o();const t=m.querySelector(".navbar-container")||m;t.insertBefore(n,t.firstChild?.nextSibling||null);const e=a=>{v.toggle("dm-vnav-autohide",a),v.toggle("dm-vnav-collapsible",!a&&L.has("collapsible")),a?v.remove("dm-vnav-collapsed"):L.has("collapsible")&&S.get("nav_collapsed")===!0&&v.add("dm-vnav-collapsed"),m.classList.remove("dm-nav-revealed"),o()};m.addEventListener("pointerdown",a=>{v.contains("dm-vnav-autohide")&&a.pointerType!=="mouse"&&!m.classList.contains("dm-nav-revealed")&&(m.classList.add("dm-nav-revealed"),a.preventDefault())}),document.addEventListener("pointerdown",a=>{m.contains(a.target)||m.classList.remove("dm-nav-revealed")}),window.E&&typeof E.contextMenu=="function"&&E.contextMenu(m,{items:()=>{const a=v.contains("dm-vnav-autohide"),d=v.contains("dm-vnav-collapsed"),p=S.has("nav_autohide")||S.has("nav_collapsed");return[{header:"Menu"},{label:d?"Expand menu":"Collapse to icons",icon:d?"maximize":"minimize",disabled:!C()||a||!L.has("collapsible"),action:()=>s(!d)},{label:a?"Keep on screen":"Hide when not in use",icon:a?"eye":"eye-off",disabled:!C(),action:()=>{S.set("nav_autohide",!a),e(!a)}},{type:"divider"},{label:"Reset to the site's setting",icon:"refresh",disabled:!p,action:()=>{S.remove("nav_autohide"),S.remove("nav_collapsed"),e(L.has("autohide")),v.toggle("dm-vnav-collapsed",!L.has("autohide")&&L.has("start-collapsed")),o()}}]}})}function $e(m){m.classList.add("dm-nav-scroll-hide");let c=window.scrollY,x=!1;window.addEventListener("scroll",()=>{x||(x=!0,requestAnimationFrame(()=>{x=!1;const v=window.scrollY,L=m.matches(":focus-within")||m.querySelector(".navbar-dropdown.open, .navbar-collapse.show");v>c&&v>m.offsetHeight&&!L?m.classList.add("dm-nav-scrolled-away"):v<c&&m.classList.remove("dm-nav-scrolled-away"),c=v}))},{passive:!0})}function Ee(m){let c={};try{c=JSON.parse(m.dataset.contents||"{}")}catch{}const x=m.closest(".site-main"),v=document.querySelector(".site-content"),L=new Set(String(c.exclude||"").split(",").map(i=>i.trim().toLowerCase()).filter(Boolean)),C=i=>!i.closest(".hero, .card, .accordion, .tabs, figure, #site-sidebar"),n=i=>(v?[...v.querySelectorAll(i)]:[]).filter(C).filter(r=>!L.has(r.textContent.trim().toLowerCase())),o=c.headings==="h2"?n("h2"):c.headings==="h3"?n("h3"):c.headings==="h2h3"?n("h2, h3"):n("h2").length?n("h2"):n("h3");if(!o.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",c.title||"Page contents"),c.hover&&(t.dataset.menuHover=c.hover),c.selected&&(t.dataset.menuActive=c.selected),c.accent&&t.style.setProperty("--dm-menu-highlight",c.accent.startsWith("#")?c.accent:`var(--dm-${c.accent})`),c.font&&(t.style.fontFamily=c.font),c.fontSize&&(t.style.fontSize=c.fontSize),c.fontWeight&&(t.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),t.appendChild(e);const d=document.createElement("ol");d.className="dm-contents-list"+(c.numbered?" is-numbered":"");const p=[];for(const i of o){if(!i.id){const b=(i.textContent||"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"section";let u=b;for(let y=2;s.has(u);y++)u=`${b}-${y}`;s.add(u),i.id=u}const r=document.createElement("li");r.className=`dm-contents-item dm-contents-${i.tagName.toLowerCase()}`;const g=document.createElement("a");g.href=`#${i.id}`,g.textContent=i.textContent.trim(),r.appendChild(g),d.appendChild(r),p.push([i,g])}if(t.appendChild(d),m.replaceChildren(t),c.scrollSpy!==!1&&"IntersectionObserver"in window){const i=new Map,r=()=>{const b=p.find(([u])=>i.get(u))||null;p.forEach(([,u])=>{const y=b&&u===b[1];u.classList.toggle("is-active",!!y),y?u.setAttribute("aria-current","location"):u.removeAttribute("aria-current")})},g=new IntersectionObserver(b=>{b.forEach(u=>i.set(u.target,u.isIntersecting)),r()},{rootMargin:"-80px 0px -60% 0px"});p.forEach(([b])=>g.observe(b))}if(x?.classList.contains("contents-collapsible")){const i=document.createElement("button");i.type="button",i.className="dm-contents-toggle";const r=()=>{const g=x.classList.contains("contents-collapsed");i.setAttribute("aria-expanded",String(!g)),i.setAttribute("aria-label",g?"Show the contents":"Hide the contents"),i.title=i.getAttribute("aria-label"),i.innerHTML=`<span data-icon="${g?"list":"x"}"></span>`,window.Domma?.icons&&Domma.icons.scan(i)};i.addEventListener("click",()=>{S.set("contents_collapsed",x.classList.toggle("contents-collapsed")),r()}),r(),e.insertBefore(i,e.firstChild)}if(x?.classList.contains("contents-floating")&&c.movable){t.classList.add("is-movable");const i=S.get("contents_position");i&&Number.isFinite(i.x)&&Object.assign(m.style,{left:`${i.x}px`,top:`${i.y}px`,right:"auto",bottom:"auto"}),e.addEventListener("pointerdown",r=>{if(r.target.closest("button"))return;const g=m.getBoundingClientRect(),b=r.clientX-g.left,u=r.clientY-g.top,y=f=>{const w=Math.max(0,Math.min(window.innerWidth-g.width,f.clientX-b)),q=Math.max(0,Math.min(window.innerHeight-40,f.clientY-u));Object.assign(m.style,{left:`${w}px`,top:`${q}px`,right:"auto",bottom:"auto"})},h=()=>{document.removeEventListener("pointermove",y),S.set("contents_position",{x:parseInt(m.style.left,10),y:parseInt(m.style.top,10)})};document.addEventListener("pointermove",y),document.addEventListener("pointerup",h,{once:!0})})}window.E&&typeof E.contextMenu=="function"&&E.contextMenu(m,{items:()=>{const i=x?.classList.contains("contents-collapsible"),r=x?.classList.contains("contents-collapsed"),g=p.find(([,b])=>b.classList.contains("is-active"));return[{header:"Contents"},...i?[{label:r?"Show the contents":"Collapse to a button",icon:r?"list":"minimize",action:()=>m.querySelector(".dm-contents-toggle")?.click()}]:[],{label:"Copy a link to this section",icon:"link",disabled:!g,action:async()=>{const b=`${location.origin}${location.pathname}#${g[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")&&(m.addEventListener("pointerdown",i=>{i.pointerType!=="mouse"&&!m.classList.contains("is-revealed")&&(m.classList.add("is-revealed"),i.preventDefault())}),document.addEventListener("pointerdown",i=>{m.contains(i.target)||m.classList.remove("is-revealed")}))}
@@ -23,11 +23,11 @@ import {keepSubmissionsOut} from '../../services/siteGitignore.js';
23
23
  import {createRequire} from 'module';
24
24
  import {fileURLToPath} from 'url';
25
25
  import path from 'path';
26
- import {deleteForm, ensureCollectionForForm, ensureFormsDir, listForms, readForm, slugify, writeForm} from '../../services/forms.js';
26
+ import {deleteForm, ensureCollectionForForm, ensureFormsDir, listForms, maxSubmissionBytes, publicFileLimits, readForm, slugify, writeForm} from '../../services/forms.js';
27
27
  import {executeAction} from '../../services/actions.js';
28
28
  // The same rules the browser draws the buttons by (a Cancel link must be safe).
29
29
  import {buttonSettingsProblems, formClassProblems, publicButtonSettings} from '../../../public/js/form-submit.mjs';
30
- import {cleanUploadName, countRefusal, fileRefusal, fileRules, fileValueText, isFileValue} from '../../../public/js/form-files.mjs';
30
+ import {cleanUploadName, countRefusal, fileRefusal, submissionTooLarge, fileRules, fileValueText, isFileValue} from '../../../public/js/form-files.mjs';
31
31
  import {sanitiseCsv} from '../../../public/js/collection-export.mjs';
32
32
  import {originOf, trustedOrigin} from '../../services/passwordReset.js';
33
33
  import {createTransport, mailFailure, sendFormEmail, TEST_EMAIL_FAILED} from '../../services/email.js';
@@ -148,31 +148,9 @@ function validateUpload(fieldCfg, part, size) {
148
148
  || servedTypeRefusal(fieldCfg, part.filename || '');
149
149
  }
150
150
 
151
- /** What one submission's files may hold in memory together when server.json sets nothing: 50 MB. */
152
- export const DEFAULT_MAX_SUBMISSION_BYTES = 50 * 1024 * 1024;
153
-
154
- /**
155
- * The most bytes one submission's files may hold in memory, all fields together:
156
- * server.json `uploads.maxSubmissionBytes` (read fresh) when set; otherwise 50 MB or
157
- * `uploads.maxFileSize`, whichever is larger, so one file the site takes is never
158
- * refused for the total.
159
- *
160
- * @returns {number}
161
- */
162
- export function maxSubmissionBytes() {
163
- let uploads = {};
164
- try { uploads = getConfig('server')?.uploads || {}; } catch { /* no server.json */ }
165
- const set = Number(uploads.maxSubmissionBytes);
166
- if (set > 0) return set;
167
- return Math.max(DEFAULT_MAX_SUBMISSION_BYTES, Number(uploads.maxFileSize) || 0);
168
- }
169
-
170
- /** The refusal for a submission whose files together pass maxSubmissionBytes(). */
171
- export function submissionTooLarge(limit) {
172
- const mb = limit / (1024 * 1024);
173
- const size = mb >= 1 ? `${Math.round(mb * 10) / 10} MB` : `${Math.round(limit / 1024)} KB`;
174
- return `The files sent are too large together (at most ${size} in one submission). Send fewer or smaller files.`;
175
- }
151
+ // The per-submission cap lives in services/forms.js (the page is told it too), its words in form-files.mjs.
152
+ export {DEFAULT_MAX_SUBMISSION_BYTES, maxSubmissionBytes} from '../../services/forms.js';
153
+ export {submissionTooLarge};
176
154
 
177
155
  /**
178
156
  * Drain a multipart request into a JSON-shaped body. Text parts become string
@@ -660,7 +638,7 @@ export async function formsRoutes(fastify) {
660
638
  // A Cancel link saved before the check (or written by hand) is
661
639
  // not handed to the page.
662
640
  if (safe.settings) safe.settings = publicButtonSettings(safe.settings);
663
- return safe;
641
+ return publicFileLimits(safe);
664
642
  } catch {
665
643
  return reply.status(404).send({ error: 'Form not found.' });
666
644
  }
@@ -9,6 +9,38 @@ import {fileURLToPath} from 'url';
9
9
  import * as cache from './cache/index.js';
10
10
  import {buttonSettingsProblems, formClassProblems} from '../../public/js/form-submit.mjs';
11
11
  import {fileIn} from './safePath.js';
12
+ import {getConfig} from '../config.js';
13
+
14
+ /** What one submission's files may hold in memory together when server.json sets nothing: 50 MB. */
15
+ export const DEFAULT_MAX_SUBMISSION_BYTES = 50 * 1024 * 1024;
16
+
17
+ /**
18
+ * The most bytes one submission's files may hold in memory, all fields together:
19
+ * server.json `uploads.maxSubmissionBytes` (read fresh) when set; otherwise 50 MB or
20
+ * `uploads.maxFileSize`, whichever is larger, so one file the site takes is never
21
+ * refused for the total.
22
+ *
23
+ * @returns {number}
24
+ */
25
+ export function maxSubmissionBytes() {
26
+ let uploads = {};
27
+ try { uploads = getConfig('server')?.uploads || {}; } catch { /* no server.json */ }
28
+ const set = Number(uploads.maxSubmissionBytes);
29
+ if (set > 0) return set;
30
+ return Math.max(DEFAULT_MAX_SUBMISSION_BYTES, Number(uploads.maxFileSize) || 0);
31
+ }
32
+
33
+ /**
34
+ * A form as a page draws it, told the per-submission file cap when it has a file field,
35
+ * so the page refuses a file that would pass it before anything is sent.
36
+ *
37
+ * @param {object} form
38
+ * @returns {object}
39
+ */
40
+ export function publicFileLimits(form) {
41
+ const hasFiles = (form?.fields || []).some(f => f && f.type === 'file');
42
+ return hasFiles ? {...form, fileLimits: {maxTotalBytes: maxSubmissionBytes()}} : form;
43
+ }
12
44
 
13
45
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
46
  const ROOT = path.resolve(__dirname, '..', '..');
@@ -44,6 +76,11 @@ export async function readForm(slug) {
44
76
  */
45
77
  export async function writeForm(slug, data) {
46
78
  await ensureFormsDir();
79
+ // fileLimits is the server's config, added to the copy a page is given - never stored
80
+ if (data && 'fileLimits' in data) {
81
+ const {fileLimits: _limits, ...rest} = data;
82
+ data = rest;
83
+ }
47
84
  const file = fileIn(FORMS_DIR, slug, '.json');
48
85
  await fs.writeFile(file, JSON.stringify(data, null, 4) + '\n', 'utf8');
49
86
  await cache.invalidateTags([`form:${slug}`]);
@@ -23,6 +23,7 @@ import {buildTextStyle} from '../../public/js/text-style.mjs';
23
23
  import {buildScribeStyle} from '../../public/js/scribe-style.mjs';
24
24
  import {CHARACTERS, resolveCharacterSize} from '../../public/js/celebrations/character-catalog.mjs';
25
25
  import {publicButtonSettings} from '../../public/js/form-submit.mjs';
26
+ import {publicFileLimits} from './forms.js';
26
27
  import {applyShortcodeExpiry} from './shortcodeExpiry.js';
27
28
 
28
29
  const __dirname_md = path.dirname(fileURLToPath(import.meta.url));
@@ -4461,7 +4462,7 @@ async function processFormBlocks(markdown, tagSet) {
4461
4462
  const {actions: _actions, ...publicForm} = form;
4462
4463
  // A Cancel link that is not safe to draw stays out of the page source.
4463
4464
  if (publicForm.settings) publicForm.settings = publicButtonSettings(publicForm.settings);
4464
- const encoded = Buffer.from(JSON.stringify(publicForm)).toString('base64');
4465
+ const encoded = Buffer.from(JSON.stringify(publicFileLimits(publicForm))).toString('base64');
4465
4466
  const extraClass = attrs.class ? ` ${escapeAttr(attrs.class)}` : '';
4466
4467
  const idAttr = attrs.id ? ` id="${escapeAttr(attrs.id)}"` : '';
4467
4468
  replacement = `<div class="cms-form-embed${extraClass}" data-form-inline="${escapeAttr(encoded)}"${idAttr}>${formSummaryHtml(form)}</div>`;
@@ -185,7 +185,7 @@
185
185
  <script src="/public/js/form-logic-engine.js?v=20261025-dropzone-disable" defer></script>
186
186
 
187
187
  <!-- Site initialisation -->
188
- <script src="/public/js/site.js?v=20261025-dropzone-range" type="module"></script>
188
+ <script src="/public/js/site.js?v=20261025-files-total" type="module"></script>
189
189
  <script src="/public/js/collection-context.js?v=20261010-utf8-payloads" type="module"></script>
190
190
  {{ctxMenusModule}}
191
191
 
@@ -195,7 +195,7 @@
195
195
  <script src="/public/js/menu-float.js?v=20260927-float" type="module"></script>
196
196
 
197
197
  <!-- Core Forms renderer -->
198
- <script src="/public/js/forms.js?v=20261025-form-dropzone" type="module"></script>
198
+ <script src="/public/js/forms.js?v=20261025-forms-files-total" type="module"></script>
199
199
 
200
200
  <!-- Core effects runtime -->
201
201
  <script src="/public/js/effects.js?v=20261015-occasions" defer></script>